hilda 2.0.15__tar.gz → 3.0.0__tar.gz
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-2.0.15 → hilda-3.0.0}/.pre-commit-config.yaml +1 -1
- {hilda-2.0.15 → hilda-3.0.0}/PKG-INFO +17 -15
- {hilda-2.0.15 → hilda-3.0.0}/README.md +15 -14
- {hilda-2.0.15 → hilda-3.0.0}/hilda/_version.py +9 -4
- hilda-3.0.0/hilda/breakpoints.py +480 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/hilda_client.py +88 -306
- hilda-3.0.0/hilda/ipython_extensions/keybindings.py +47 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/objective_c_class.py +2 -6
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/mach/CFRunLoopServiceMachPort_hooks.py +2 -5
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/macho/all_image_infos.py +7 -4
- {hilda-2.0.15 → hilda-3.0.0}/hilda/symbol.py +3 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda.egg-info/PKG-INFO +17 -15
- {hilda-2.0.15 → hilda-3.0.0}/hilda.egg-info/SOURCES.txt +1 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda.egg-info/requires.txt +1 -0
- {hilda-2.0.15 → hilda-3.0.0}/requirements.txt +2 -1
- {hilda-2.0.15 → hilda-3.0.0}/tests/test_hilda_client/test_monitor.py +1 -1
- hilda-2.0.15/hilda/ipython_extensions/keybindings.py +0 -24
- {hilda-2.0.15 → hilda-3.0.0}/.github/workflows/python-app.yml +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/.github/workflows/python-publish.yml +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/.gitignore +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/LICENSE +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/gifs/.gitattributes +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/gifs/ui.png +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/gifs/xpc_print_message.gif +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/__init__.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/__main__.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/cli.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/common.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/exceptions.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/hilda_ascii_art.html +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/ipython_extensions/events.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/ipython_extensions/magics.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/launch_lldb.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/lldb_entrypoint.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/lldb_importer.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/objective_c/from_ns_to_json.m +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/objective_c/get_objectivec_class_by_module.m +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/objective_c/get_objectivec_class_description.m +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/objective_c/get_objectivec_symbol_data.m +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/objective_c/lsof.m +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/objective_c/to_ns_from_json.m +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/objective_c_symbol.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/registers.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/__init__.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/boringssl.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/collections.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/dyld.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/fs_utils.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/libmalloc.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/mach/__init__.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/macho/__init__.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/macho/apple_version.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/macho/image_info.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/macho/macho.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/macho/macho_load_commands.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/remotepairingd.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/syslog.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/uuid.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/snippets/xpc.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/symbols_jar.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/ui/colors.json +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/ui/ui_manager.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda/ui/views.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda.egg-info/dependency_links.txt +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda.egg-info/entry_points.txt +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/hilda.egg-info/top_level.txt +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/pyproject.toml +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/setup.cfg +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/__init__.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/conftest.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/test_hilda_client/test_from_ns.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/test_hilda_client/test_hilda_client.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/test_hilda_client/test_ns.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/test_hilda_client/test_rebind_symbols.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/test_hilda_client/test_registers.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/test_snippets/test_xpc.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/test_symbols/test_objective_c_class.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/test_symbols/test_objective_c_symbol.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/test_symbols/test_symbol.py +0 -0
- {hilda-2.0.15 → hilda-3.0.0}/tests/test_symbols/test_symbols_jar.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: hilda
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
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>
|
|
@@ -52,6 +52,7 @@ Requires-Dist: pymobiledevice3
|
|
|
52
52
|
Requires-Dist: keystone-engine
|
|
53
53
|
Requires-Dist: tabulate
|
|
54
54
|
Requires-Dist: inquirer3
|
|
55
|
+
Requires-Dist: traitlets
|
|
55
56
|
Provides-Extra: test
|
|
56
57
|
Requires-Dist: pytest; extra == "test"
|
|
57
58
|
|
|
@@ -162,7 +163,7 @@ Have a nice flight ✈️! Starting an IPython shell...
|
|
|
162
163
|
Here is a gist of methods you can access from `p`:
|
|
163
164
|
|
|
164
165
|
- `hd`
|
|
165
|
-
- Print
|
|
166
|
+
- Print a hexdump of given buffer
|
|
166
167
|
- `lsof`
|
|
167
168
|
- Get dictionary of all open FDs
|
|
168
169
|
- `bt`
|
|
@@ -185,6 +186,8 @@ Here is a gist of methods you can access from `p`:
|
|
|
185
186
|
- Read data at given address
|
|
186
187
|
- `peek_str`
|
|
187
188
|
- Peek a buffer till null termination
|
|
189
|
+
- `peek_std_str`
|
|
190
|
+
- Peek a `std::string`
|
|
188
191
|
- `stop`
|
|
189
192
|
- Stop process.
|
|
190
193
|
- `cont`
|
|
@@ -207,7 +210,7 @@ Here is a gist of methods you can access from `p`:
|
|
|
207
210
|
- Simulate a call to an objc selector
|
|
208
211
|
- `call`
|
|
209
212
|
- Call function at given address with given parameters
|
|
210
|
-
- `monitor`
|
|
213
|
+
- `monitor` or `breakpoints.add_monitor`
|
|
211
214
|
- Monitor every time a given address is called
|
|
212
215
|
|
|
213
216
|
The following options are available:
|
|
@@ -252,10 +255,10 @@ Here is a gist of methods you can access from `p`:
|
|
|
252
255
|
- Step into current instruction.
|
|
253
256
|
- `step_over`
|
|
254
257
|
- Step over current instruction.
|
|
255
|
-
- `
|
|
256
|
-
- Remove all breakpoints
|
|
257
|
-
- `
|
|
258
|
-
- Remove a single breakpoint
|
|
258
|
+
- `breakpoints.clear`
|
|
259
|
+
- Remove all breakpoints
|
|
260
|
+
- `breakpoints.remove`
|
|
261
|
+
- Remove a single breakpoint
|
|
259
262
|
- `force_return`
|
|
260
263
|
- Prematurely return from a stack frame, short-circuiting exection of newer frames and optionally
|
|
261
264
|
yielding a specified value.
|
|
@@ -263,14 +266,10 @@ Here is a gist of methods you can access from `p`:
|
|
|
263
266
|
- Print information about currently running mapped process.
|
|
264
267
|
- `print_proc_entitlements`
|
|
265
268
|
- Get the plist embedded inside the process' __LINKEDIT section.
|
|
266
|
-
- `bp`
|
|
269
|
+
- `bp` or `breakpoints.add`
|
|
267
270
|
- Add a breakpoint
|
|
268
|
-
- `
|
|
269
|
-
- Show existing breakpoints
|
|
270
|
-
- `save`
|
|
271
|
-
- Save loaded symbols map (for loading later using the load() command)
|
|
272
|
-
- `load`
|
|
273
|
-
- Load an existing symbols map (previously saved by the save() command)
|
|
271
|
+
- `breakpoints.show`
|
|
272
|
+
- Show existing breakpoints
|
|
274
273
|
- `po`
|
|
275
274
|
- Print given object using LLDB's po command
|
|
276
275
|
Can also run big chunks of native code:
|
|
@@ -324,6 +323,9 @@ Sometimes accessing the [Python API](#python-api) can be tiring, so we added som
|
|
|
324
323
|
|
|
325
324
|
#### Key-bindings
|
|
326
325
|
|
|
326
|
+
- **F1**: Show banner help message
|
|
327
|
+
- **F2**: Show process state UI
|
|
328
|
+
- **F3**: Toggle stdout/stderr enablement
|
|
327
329
|
- **F7**: Step Into
|
|
328
330
|
- **F8**: Step Over
|
|
329
331
|
- **F9**: Continue
|
|
@@ -529,7 +531,7 @@ s.bp(scripted_breakpoint)
|
|
|
529
531
|
p.bp('symbol_name')
|
|
530
532
|
|
|
531
533
|
# In case you need to specify a specific library it's loaded from
|
|
532
|
-
p.bp('symbol_name',
|
|
534
|
+
p.bp(('symbol_name', 'ModuleName'))
|
|
533
535
|
```
|
|
534
536
|
|
|
535
537
|
#### Globalized symbols
|
|
@@ -105,7 +105,7 @@ Have a nice flight ✈️! Starting an IPython shell...
|
|
|
105
105
|
Here is a gist of methods you can access from `p`:
|
|
106
106
|
|
|
107
107
|
- `hd`
|
|
108
|
-
- Print
|
|
108
|
+
- Print a hexdump of given buffer
|
|
109
109
|
- `lsof`
|
|
110
110
|
- Get dictionary of all open FDs
|
|
111
111
|
- `bt`
|
|
@@ -128,6 +128,8 @@ Here is a gist of methods you can access from `p`:
|
|
|
128
128
|
- Read data at given address
|
|
129
129
|
- `peek_str`
|
|
130
130
|
- Peek a buffer till null termination
|
|
131
|
+
- `peek_std_str`
|
|
132
|
+
- Peek a `std::string`
|
|
131
133
|
- `stop`
|
|
132
134
|
- Stop process.
|
|
133
135
|
- `cont`
|
|
@@ -150,7 +152,7 @@ Here is a gist of methods you can access from `p`:
|
|
|
150
152
|
- Simulate a call to an objc selector
|
|
151
153
|
- `call`
|
|
152
154
|
- Call function at given address with given parameters
|
|
153
|
-
- `monitor`
|
|
155
|
+
- `monitor` or `breakpoints.add_monitor`
|
|
154
156
|
- Monitor every time a given address is called
|
|
155
157
|
|
|
156
158
|
The following options are available:
|
|
@@ -195,10 +197,10 @@ Here is a gist of methods you can access from `p`:
|
|
|
195
197
|
- Step into current instruction.
|
|
196
198
|
- `step_over`
|
|
197
199
|
- Step over current instruction.
|
|
198
|
-
- `
|
|
199
|
-
- Remove all breakpoints
|
|
200
|
-
- `
|
|
201
|
-
- Remove a single breakpoint
|
|
200
|
+
- `breakpoints.clear`
|
|
201
|
+
- Remove all breakpoints
|
|
202
|
+
- `breakpoints.remove`
|
|
203
|
+
- Remove a single breakpoint
|
|
202
204
|
- `force_return`
|
|
203
205
|
- Prematurely return from a stack frame, short-circuiting exection of newer frames and optionally
|
|
204
206
|
yielding a specified value.
|
|
@@ -206,14 +208,10 @@ Here is a gist of methods you can access from `p`:
|
|
|
206
208
|
- Print information about currently running mapped process.
|
|
207
209
|
- `print_proc_entitlements`
|
|
208
210
|
- Get the plist embedded inside the process' __LINKEDIT section.
|
|
209
|
-
- `bp`
|
|
211
|
+
- `bp` or `breakpoints.add`
|
|
210
212
|
- Add a breakpoint
|
|
211
|
-
- `
|
|
212
|
-
- Show existing breakpoints
|
|
213
|
-
- `save`
|
|
214
|
-
- Save loaded symbols map (for loading later using the load() command)
|
|
215
|
-
- `load`
|
|
216
|
-
- Load an existing symbols map (previously saved by the save() command)
|
|
213
|
+
- `breakpoints.show`
|
|
214
|
+
- Show existing breakpoints
|
|
217
215
|
- `po`
|
|
218
216
|
- Print given object using LLDB's po command
|
|
219
217
|
Can also run big chunks of native code:
|
|
@@ -267,6 +265,9 @@ Sometimes accessing the [Python API](#python-api) can be tiring, so we added som
|
|
|
267
265
|
|
|
268
266
|
#### Key-bindings
|
|
269
267
|
|
|
268
|
+
- **F1**: Show banner help message
|
|
269
|
+
- **F2**: Show process state UI
|
|
270
|
+
- **F3**: Toggle stdout/stderr enablement
|
|
270
271
|
- **F7**: Step Into
|
|
271
272
|
- **F8**: Step Over
|
|
272
273
|
- **F9**: Continue
|
|
@@ -472,7 +473,7 @@ s.bp(scripted_breakpoint)
|
|
|
472
473
|
p.bp('symbol_name')
|
|
473
474
|
|
|
474
475
|
# In case you need to specify a specific library it's loaded from
|
|
475
|
-
p.bp('symbol_name',
|
|
476
|
+
p.bp(('symbol_name', 'ModuleName'))
|
|
476
477
|
```
|
|
477
478
|
|
|
478
479
|
#### Globalized symbols
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '
|
|
16
|
-
__version_tuple__ = version_tuple = (
|
|
20
|
+
__version__ = version = '3.0.0'
|
|
21
|
+
__version_tuple__ = version_tuple = (3, 0, 0)
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
from typing import Any, Callable, Generator, Optional, Union
|
|
2
|
+
|
|
3
|
+
import inquirer3
|
|
4
|
+
|
|
5
|
+
from hilda.lldb_importer import lldb
|
|
6
|
+
from hilda.symbol import Symbol
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
A value identifying where the breakpoint was set (when it was created)
|
|
10
|
+
|
|
11
|
+
It could be either an address (int), a symbol name (string), a symbol name in a
|
|
12
|
+
specific module (Tuple[str, str], where the first item is the symbol name and the
|
|
13
|
+
second is the module name) or a Hilda symbol object (Symbol, that inherits from int).
|
|
14
|
+
"""
|
|
15
|
+
WhereType = Union[int, str, tuple[str, str], Symbol]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HildaBreakpoint:
|
|
19
|
+
"""
|
|
20
|
+
Hilda's class representing an LLDB breakpoint, with some optional additional properties
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, hilda, lldb_breakpoint: lldb.SBBreakpoint,
|
|
24
|
+
where: Optional[WhereType] = None, description: Optional[str] = None) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Initialize a HildaBreakpoint.
|
|
27
|
+
|
|
28
|
+
:param hilda.hilda_client.HildaClient hilda: Hilda client
|
|
29
|
+
:param lldb.SBBreakpoint lldb_breakpoint: LLDB breakpoint to wrap
|
|
30
|
+
:param WhereType where: Where the breakpoint is located
|
|
31
|
+
:param description: Description of the breakpoint to appear upon `hilda.breakpoints.show()`
|
|
32
|
+
"""
|
|
33
|
+
self._hilda = hilda
|
|
34
|
+
self._where = where
|
|
35
|
+
self._callback = None
|
|
36
|
+
|
|
37
|
+
# Actual breakpoint from LLDB API
|
|
38
|
+
self.lldb_breakpoint = lldb_breakpoint
|
|
39
|
+
|
|
40
|
+
# If true, breakpoint will not be removed unless `remove_guarded` is requested
|
|
41
|
+
self.guarded = False
|
|
42
|
+
|
|
43
|
+
# Attach a description to the breakpoint
|
|
44
|
+
self.description = description
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def where(self) -> Optional[WhereType]:
|
|
48
|
+
""" Where the breakpoint was set (when it was created). """
|
|
49
|
+
return self._where
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def id(self) -> int:
|
|
53
|
+
""" A number identifying the breakpoint. """
|
|
54
|
+
return self.lldb_breakpoint.GetID()
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def callback(self) -> Optional[Callable]:
|
|
58
|
+
"""
|
|
59
|
+
A callback that will be executed when the breakpoint is hit.
|
|
60
|
+
Note that unless the callback explicitly continues (by calling `cont()`), the program will not continue.
|
|
61
|
+
The callback will be invoked as `callback(hilda, *args)`, where hilda is the `HildaClient`.
|
|
62
|
+
"""
|
|
63
|
+
return self._callback
|
|
64
|
+
|
|
65
|
+
@callback.setter
|
|
66
|
+
def callback(self, callback: Optional[Callable]) -> None:
|
|
67
|
+
self._callback = callback
|
|
68
|
+
|
|
69
|
+
if callback is not None:
|
|
70
|
+
self.lldb_breakpoint.SetScriptCallbackFunction(
|
|
71
|
+
'lldb.hilda_client.breakpoints._dispatch_breakpoint_callback')
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def condition(self) -> Optional[str]:
|
|
75
|
+
""" An LLDB expression to make this a conditional breakpoint. """
|
|
76
|
+
return self.lldb_breakpoint.GetCondition()
|
|
77
|
+
|
|
78
|
+
@condition.setter
|
|
79
|
+
def condition(self, condition: Optional[str]) -> None:
|
|
80
|
+
self.lldb_breakpoint.SetCondition(condition)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def locations(self) -> list[lldb.SBBreakpointLocation]:
|
|
84
|
+
""" LLDB locations array the breakpoint relates to. """
|
|
85
|
+
return self.lldb_breakpoint.locations
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def name(self) -> Optional[str]:
|
|
89
|
+
"""
|
|
90
|
+
A name for the breakpoint.
|
|
91
|
+
When getting, if the breakpoint has multiple names, raises an exception.
|
|
92
|
+
"""
|
|
93
|
+
names = self.names
|
|
94
|
+
if len(names) == 0:
|
|
95
|
+
return None
|
|
96
|
+
if len(names) != 1:
|
|
97
|
+
raise ValueError(f'Breakpoint {self.id} has multiple names {names}')
|
|
98
|
+
|
|
99
|
+
name, = names
|
|
100
|
+
return name
|
|
101
|
+
|
|
102
|
+
@name.setter
|
|
103
|
+
def name(self, name: Optional[str]) -> None:
|
|
104
|
+
self.names = {name}
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def names(self) -> set[str]:
|
|
108
|
+
""" The set of names of the breakpoint. """
|
|
109
|
+
name_list = lldb.SBStringList()
|
|
110
|
+
self.lldb_breakpoint.GetNames(name_list)
|
|
111
|
+
return set(name_list.GetStringAtIndex(i) for i in range(name_list.GetSize()))
|
|
112
|
+
|
|
113
|
+
@names.setter
|
|
114
|
+
def names(self, names: Union[set[str], list[str]]) -> None:
|
|
115
|
+
new_names = set(names)
|
|
116
|
+
if len(new_names) != len(names):
|
|
117
|
+
raise ValueError(f'Duplicate names in {names}')
|
|
118
|
+
|
|
119
|
+
current_names = self.names
|
|
120
|
+
names_to_remove = current_names - new_names
|
|
121
|
+
names_to_add = new_names - current_names
|
|
122
|
+
for name in names_to_remove:
|
|
123
|
+
self.lldb_breakpoint.RemoveName(name)
|
|
124
|
+
for name in names_to_add:
|
|
125
|
+
self.lldb_breakpoint.AddName(name)
|
|
126
|
+
|
|
127
|
+
def __repr__(self) -> str:
|
|
128
|
+
return (f'<{self.__class__.__name__} LLDB:{self.lldb_breakpoint} GUARDED:{self.guarded} '
|
|
129
|
+
f'CALLBACK:{self.callback}>')
|
|
130
|
+
|
|
131
|
+
def __str__(self) -> str:
|
|
132
|
+
result = f'🚨 Breakpoint #{self.id} (guarded: {self.guarded}):\n'
|
|
133
|
+
if self.description is not None:
|
|
134
|
+
result += f'\tDescription: {self.description}\n'
|
|
135
|
+
|
|
136
|
+
if self.where is not None:
|
|
137
|
+
result += f'\tWhere: {self.where}\n'
|
|
138
|
+
|
|
139
|
+
# A single breakpoint may be related to several locations (addresses)
|
|
140
|
+
for location in self.locations:
|
|
141
|
+
result += f'\tLocation {location}\n'
|
|
142
|
+
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
def remove(self, remove_guarded: bool = False) -> None:
|
|
146
|
+
"""
|
|
147
|
+
Remove the breakpoint (unless the breakpoint is marked as guarded, see remove_guarded argument)
|
|
148
|
+
|
|
149
|
+
:param bool remove_guarded: Remove the breakpoint even if the breakpoint is guarded
|
|
150
|
+
"""
|
|
151
|
+
self._hilda.breakpoints.remove(self, remove_guarded)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class BreakpointList:
|
|
155
|
+
"""
|
|
156
|
+
Manager for `HildaBreakpoint` objects, each one wrapping another native LLDB breakpoint.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
def __init__(self, hilda) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Initialize a breakpoint list.
|
|
162
|
+
|
|
163
|
+
:param hilda.hilda_client.HildaClient hilda: Hilda client
|
|
164
|
+
"""
|
|
165
|
+
self._hilda = hilda
|
|
166
|
+
self._breakpoints = {}
|
|
167
|
+
|
|
168
|
+
def __contains__(self, id_or_name_or_bp: Union[int, str, HildaBreakpoint]):
|
|
169
|
+
return self.get(id_or_name_or_bp) is not None
|
|
170
|
+
|
|
171
|
+
def __iter__(self):
|
|
172
|
+
for bp in self._hilda.target.breakpoint_iter():
|
|
173
|
+
yield self[bp.GetID()]
|
|
174
|
+
|
|
175
|
+
def __len__(self) -> int:
|
|
176
|
+
return self._hilda.target.GetNumBreakpoints()
|
|
177
|
+
|
|
178
|
+
def __getitem__(self, id_or_name_or_bp: Union[int, str, HildaBreakpoint]) -> HildaBreakpoint:
|
|
179
|
+
"""
|
|
180
|
+
Get a breakpoint by ID or name (or the breakpoint itself, though it usually makes little sense)
|
|
181
|
+
|
|
182
|
+
:param id_or_name_or_bp: Breakpoint's ID or name (or the breakpoint itself)
|
|
183
|
+
"""
|
|
184
|
+
bp = self.get(id_or_name_or_bp)
|
|
185
|
+
if bp is None:
|
|
186
|
+
raise KeyError(id_or_name_or_bp)
|
|
187
|
+
return bp
|
|
188
|
+
|
|
189
|
+
def __delitem__(self, id_or_name_or_bp: Union[int, str, HildaBreakpoint]) -> None:
|
|
190
|
+
"""
|
|
191
|
+
Remove a breakpoint (unless the breakpoint is marked as guarded - see remove())
|
|
192
|
+
|
|
193
|
+
:param id_or_name_or_bp: Breakpoint's ID or name (or the breakpoint itself)
|
|
194
|
+
"""
|
|
195
|
+
self.remove(id_or_name_or_bp)
|
|
196
|
+
|
|
197
|
+
def __repr__(self) -> str:
|
|
198
|
+
return repr(dict(self.items()))
|
|
199
|
+
|
|
200
|
+
def __str__(self) -> str:
|
|
201
|
+
return repr(self)
|
|
202
|
+
|
|
203
|
+
def get(self, id_or_name_or_bp: Union[int, str, HildaBreakpoint]) -> Optional[HildaBreakpoint]:
|
|
204
|
+
"""
|
|
205
|
+
Get a breakpoint by ID or name (or the breakpoint itself, though it usually makes little sense) or null
|
|
206
|
+
if it does not exist.
|
|
207
|
+
|
|
208
|
+
:param id_or_name_or_bp: Breakpoint's ID or name (or the breakpoint itself)
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
if isinstance(id_or_name_or_bp, int):
|
|
212
|
+
bp = self._hilda.target.FindBreakpointByID(id_or_name_or_bp)
|
|
213
|
+
elif isinstance(id_or_name_or_bp, str):
|
|
214
|
+
breakpoints = lldb.SBBreakpointList(self._hilda.target)
|
|
215
|
+
found = self._hilda.target.FindBreakpointsByName(id_or_name_or_bp, breakpoints)
|
|
216
|
+
if not found or breakpoints.GetSize() == 0:
|
|
217
|
+
return None
|
|
218
|
+
if breakpoints.GetSize() != 1:
|
|
219
|
+
# Error out if we found multiple breakpoints with the name
|
|
220
|
+
raise KeyError(id_or_name_or_bp)
|
|
221
|
+
bp = breakpoints.GetBreakpointAtIndex(0)
|
|
222
|
+
elif isinstance(id_or_name_or_bp, HildaBreakpoint):
|
|
223
|
+
bp = id_or_name_or_bp.lldb_breakpoint
|
|
224
|
+
else:
|
|
225
|
+
raise TypeError()
|
|
226
|
+
|
|
227
|
+
if not bp.IsValid():
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
bp_id = bp.GetID()
|
|
231
|
+
if bp_id not in self._breakpoints:
|
|
232
|
+
self._hilda.log_debug(f'Found a breakpoint added outside of the Hilda API {bp}')
|
|
233
|
+
self._breakpoints[bp_id] = HildaBreakpoint(self._hilda, bp)
|
|
234
|
+
|
|
235
|
+
return self._breakpoints[bp_id]
|
|
236
|
+
|
|
237
|
+
def add(self, where: WhereType, callback: Optional[Callable] = None, condition: str = None, guarded: bool = False,
|
|
238
|
+
override: bool = True, description: Optional[str] = None) -> HildaBreakpoint:
|
|
239
|
+
"""
|
|
240
|
+
Add a breakpoint.
|
|
241
|
+
|
|
242
|
+
:param where: The address of the breakpoint.
|
|
243
|
+
It could be either an address (int), a symbol name (string), a symbol name in a
|
|
244
|
+
specific module (Tuple[str, str], where the first item is the symbol name and the
|
|
245
|
+
second is the module name) or a Hilda symbol object (Symbol, that inherits from int).
|
|
246
|
+
:param callback: A callback that will be executed when the breakpoint is hit.
|
|
247
|
+
Note that unless the callback explicitly continues (by calling cont()), the program will not continue.
|
|
248
|
+
The callback will be invoked as callback(hilda, *args), where hilda is the 'HildaClient'.
|
|
249
|
+
:param condition: An LLDB expression to make this a conditional breakpoint.
|
|
250
|
+
:param guarded: If true, breakpoint will not be removed unless remove_guarded is requested
|
|
251
|
+
:param override: If True and an existing breakpoint with the same `where` is found, remove the old
|
|
252
|
+
breakpoint and replace it with the new breakpoint. Otherwise, prompt the user.
|
|
253
|
+
:return: The new breakpoint
|
|
254
|
+
"""
|
|
255
|
+
if where in (bp.where for bp in self):
|
|
256
|
+
if override or inquirer3.confirm('A breakpoint already exist in given location. '
|
|
257
|
+
'Would you like to delete the previous one?', True):
|
|
258
|
+
for bp in list(bp for bp in self if where == bp.where):
|
|
259
|
+
del self[bp]
|
|
260
|
+
|
|
261
|
+
if isinstance(where, int):
|
|
262
|
+
bp = self._hilda.target.BreakpointCreateByAddress(where)
|
|
263
|
+
elif isinstance(where, str):
|
|
264
|
+
# Note that the name in BreakpointCreateByName is the name of the location,
|
|
265
|
+
# not the name of the breakpoint.
|
|
266
|
+
bp = self._hilda.target.BreakpointCreateByName(where)
|
|
267
|
+
elif isinstance(where, tuple):
|
|
268
|
+
name, module = where
|
|
269
|
+
raise NotImplementedError()
|
|
270
|
+
if not bp.IsValid():
|
|
271
|
+
raise Exception(f'Failed to create breakpoint at {where}')
|
|
272
|
+
|
|
273
|
+
bp = HildaBreakpoint(self._hilda, bp, where, description=description)
|
|
274
|
+
bp.callback = callback
|
|
275
|
+
bp.condition = condition
|
|
276
|
+
bp.guarded = guarded
|
|
277
|
+
|
|
278
|
+
self._breakpoints[bp.id] = bp
|
|
279
|
+
|
|
280
|
+
self._hilda.log_info(f'Breakpoint #{bp.id} has been set')
|
|
281
|
+
return bp
|
|
282
|
+
|
|
283
|
+
def add_monitor(self, where: WhereType,
|
|
284
|
+
condition: str = None,
|
|
285
|
+
guarded: bool = False,
|
|
286
|
+
override: bool = True,
|
|
287
|
+
regs: Optional[dict[str, Union[str, Callable]]] = None,
|
|
288
|
+
expr: Optional[dict[str, Union[str, Callable]]] = None,
|
|
289
|
+
retval: Optional[Union[str, Callable]] = None,
|
|
290
|
+
stop: bool = False,
|
|
291
|
+
bt: bool = False,
|
|
292
|
+
cmd: Optional[list[str]] = None,
|
|
293
|
+
force_return: Optional[bool] = None,
|
|
294
|
+
name: Optional[str] = None,
|
|
295
|
+
description: Optional[str] = None,
|
|
296
|
+
) -> HildaBreakpoint:
|
|
297
|
+
"""
|
|
298
|
+
Monitor every time a given address is called.
|
|
299
|
+
|
|
300
|
+
Creates a breakpoint whose callback implements the requested features.
|
|
301
|
+
|
|
302
|
+
:param where: See add() for details.
|
|
303
|
+
:param condition: See add() for details.
|
|
304
|
+
:param guarded: See add() for details.
|
|
305
|
+
:param override: See add() for details.
|
|
306
|
+
:param regs: Print register values (using the provided format).
|
|
307
|
+
E.g., `regs={'x0': 'x'}` prints x0 in HEX format
|
|
308
|
+
The available formats are:
|
|
309
|
+
'x': hex
|
|
310
|
+
's': string
|
|
311
|
+
'cf': use CFCopyDescription() to get more informative description of the object
|
|
312
|
+
'po': use LLDB po command
|
|
313
|
+
'std::string': for std::string
|
|
314
|
+
Callable: user defined function, will be called like `format_function(hilda_client, value)`.
|
|
315
|
+
:param expr: Print LLDB expression (using the provided format).
|
|
316
|
+
E.g., `expr={'$x0': 'x', '$arg1': 'x'}` (to print the value of x0 and arg1).
|
|
317
|
+
The format behaves like in regs above.
|
|
318
|
+
:param retval: Print the return value of the function (using the provided format).
|
|
319
|
+
The format behaves like in regs above.
|
|
320
|
+
:param stop: If True, stop whenever the breakpoint is hit (otherwise continue debugging).
|
|
321
|
+
:param bt: Print backtrace.
|
|
322
|
+
:param cmd: A list of LLDB commands to run when the breakpoint is hit.
|
|
323
|
+
:param force_return: Return immediately from the function, returning the specified value.
|
|
324
|
+
:param name: Use the provided name instead of the symbol name automatically extracted from the calling frame.
|
|
325
|
+
:param description: Attach a brief description of the breakpoint.
|
|
326
|
+
:return: The new breakpoint
|
|
327
|
+
"""
|
|
328
|
+
|
|
329
|
+
if regs is None:
|
|
330
|
+
regs = {}
|
|
331
|
+
if expr is None:
|
|
332
|
+
expr = {}
|
|
333
|
+
if cmd is None:
|
|
334
|
+
cmd = []
|
|
335
|
+
|
|
336
|
+
def callback(hilda, frame: lldb.SBFrame, bp_loc: lldb.SBBreakpointLocation, *_) -> None:
|
|
337
|
+
"""
|
|
338
|
+
Callback function called when a breakpoint is hit.
|
|
339
|
+
|
|
340
|
+
:param hilda.hilda_client.HildaClient hilda: Hilda client to operate on
|
|
341
|
+
:param frame: LLDB frame
|
|
342
|
+
:param bp_loc: LLDB breakpoint location
|
|
343
|
+
"""
|
|
344
|
+
nonlocal regs, expr, retval, stop, bt, cmd, force_return, name
|
|
345
|
+
bp = bp_loc.GetBreakpoint()
|
|
346
|
+
symbol = hilda.symbol(hilda.frame.addr.GetLoadAddress(hilda.target))
|
|
347
|
+
thread = hilda.thread
|
|
348
|
+
printed_name = name if name is not None else str(symbol.lldb_symbol)
|
|
349
|
+
|
|
350
|
+
def format_value(fmt: Union[str, Callable], value: Symbol) -> str:
|
|
351
|
+
nonlocal hilda
|
|
352
|
+
if callable(fmt):
|
|
353
|
+
return fmt(hilda, value)
|
|
354
|
+
formatters = {
|
|
355
|
+
'x': lambda val: f'0x{int(val):x}',
|
|
356
|
+
's': lambda val: val.peek_str() if val else None,
|
|
357
|
+
'cf': lambda val: val.cf_description,
|
|
358
|
+
'po': lambda val: val.po(),
|
|
359
|
+
'std::string': hilda._std_string
|
|
360
|
+
}
|
|
361
|
+
if fmt in formatters:
|
|
362
|
+
return formatters[fmt](value)
|
|
363
|
+
else:
|
|
364
|
+
return f'{value:x} (unsupported format)'
|
|
365
|
+
|
|
366
|
+
log_message = f'🚨 #{bp.id} 0x{symbol:x} {printed_name} - Thread #{thread.idx}:{hex(thread.id)}'
|
|
367
|
+
|
|
368
|
+
if regs != {}:
|
|
369
|
+
log_message += '\nregs:'
|
|
370
|
+
for name, fmt in regs.items():
|
|
371
|
+
value = hilda.symbol(frame.FindRegister(name).unsigned)
|
|
372
|
+
log_message += f'\n\t{name} = {format_value(fmt, value)}'
|
|
373
|
+
|
|
374
|
+
if expr != {}:
|
|
375
|
+
log_message += '\nexpr:'
|
|
376
|
+
for name, fmt in expr.items():
|
|
377
|
+
value = hilda.symbol(hilda.evaluate_expression(name))
|
|
378
|
+
log_message += f'\n\t{name} = {format_value(fmt, value)}'
|
|
379
|
+
|
|
380
|
+
if force_return is not None:
|
|
381
|
+
hilda.force_return(force_return)
|
|
382
|
+
log_message += f'\nforced return: {force_return}'
|
|
383
|
+
|
|
384
|
+
if bt:
|
|
385
|
+
# bugfix: for callstacks from xpc events
|
|
386
|
+
hilda.finish()
|
|
387
|
+
for frame in hilda.bt():
|
|
388
|
+
log_message += f'\n\t{frame[0]} {frame[1]}'
|
|
389
|
+
|
|
390
|
+
if retval is not None:
|
|
391
|
+
# return from function
|
|
392
|
+
hilda.finish()
|
|
393
|
+
value = hilda.evaluate_expression('$arg1')
|
|
394
|
+
log_message += f'\nreturned: {format_value(retval, value)}'
|
|
395
|
+
|
|
396
|
+
hilda.log_info(log_message)
|
|
397
|
+
|
|
398
|
+
for command in cmd:
|
|
399
|
+
hilda.lldb_handle_command(command)
|
|
400
|
+
|
|
401
|
+
if stop:
|
|
402
|
+
hilda.log_info('Process remains stopped and focused on current thread')
|
|
403
|
+
else:
|
|
404
|
+
hilda.cont()
|
|
405
|
+
|
|
406
|
+
return self.add(where, callback, condition=condition, guarded=guarded, override=override,
|
|
407
|
+
description=description)
|
|
408
|
+
|
|
409
|
+
def remove(self, id_or_name_or_bp: Union[int, str, HildaBreakpoint], remove_guarded: bool = False) -> None:
|
|
410
|
+
"""
|
|
411
|
+
Remove a breakpoint (unless the breakpoint is marked as guarded, see remove_guarded argument).
|
|
412
|
+
|
|
413
|
+
:param id_or_name_or_bp: Breakpoint's ID or name (or the breakpoint itself)
|
|
414
|
+
:param remove_guarded: Remove breakpoint even if the breakpoint is marked as guarded
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
bp = self[id_or_name_or_bp]
|
|
418
|
+
|
|
419
|
+
if bp.guarded and not remove_guarded:
|
|
420
|
+
self._hilda.log_warning(f'Remove request for breakpoint {bp} is ignored')
|
|
421
|
+
return
|
|
422
|
+
|
|
423
|
+
# Removing a breakpoint without using this function would leak the breakpoint in self._breakpoints
|
|
424
|
+
|
|
425
|
+
breakpoint_id = bp.id
|
|
426
|
+
self._hilda.target.BreakpointDelete(breakpoint_id)
|
|
427
|
+
self._hilda.log_debug(f'Breakpoint #{breakpoint_id} has been removed')
|
|
428
|
+
|
|
429
|
+
def clear(self, remove_guarded: bool = False) -> None:
|
|
430
|
+
"""
|
|
431
|
+
Remove all breakpoints (except for breakpoints marked as guarded, see remove_guarded argument).
|
|
432
|
+
|
|
433
|
+
:param remove_guarded: Also remove breakpoints marked as guarded
|
|
434
|
+
"""
|
|
435
|
+
breakpoints = list(self)
|
|
436
|
+
for bp in breakpoints:
|
|
437
|
+
if not remove_guarded and bp.guarded:
|
|
438
|
+
continue
|
|
439
|
+
|
|
440
|
+
self.remove(bp, remove_guarded)
|
|
441
|
+
|
|
442
|
+
def show(self) -> None:
|
|
443
|
+
""" Show existing breakpoints. """
|
|
444
|
+
for bp in self:
|
|
445
|
+
print(bp)
|
|
446
|
+
|
|
447
|
+
def items(self):
|
|
448
|
+
"""
|
|
449
|
+
Get a breakpoint ID and breakpoint object tuple for every breakpoint
|
|
450
|
+
"""
|
|
451
|
+
return ((bp.id, bp) for bp in self)
|
|
452
|
+
|
|
453
|
+
def keys(self) -> Generator[int, Any, None]:
|
|
454
|
+
"""
|
|
455
|
+
Get the breakpoint ID for every breakpoint
|
|
456
|
+
"""
|
|
457
|
+
return (bp.id for bp in self)
|
|
458
|
+
|
|
459
|
+
def values(self) -> Generator[HildaBreakpoint, Any, None]:
|
|
460
|
+
"""
|
|
461
|
+
Get the breakpoint object for every breakpoint
|
|
462
|
+
"""
|
|
463
|
+
return (bp for bp in self)
|
|
464
|
+
|
|
465
|
+
def _dispatch_breakpoint_callback(self, frame, bp_loc, *_) -> None:
|
|
466
|
+
"""
|
|
467
|
+
Route the breakpoint callback the specific breakpoint callback.
|
|
468
|
+
|
|
469
|
+
:param lldb.SBFrame frame: LLDB Frame object.
|
|
470
|
+
:param lldb.SBBreakpointLocation bp_loc: LLDB Breakpoint location object.
|
|
471
|
+
"""
|
|
472
|
+
|
|
473
|
+
bp_id = bp_loc.GetBreakpoint().GetID()
|
|
474
|
+
self._hilda._bp_frame = frame
|
|
475
|
+
try:
|
|
476
|
+
callback = self[bp_id].callback
|
|
477
|
+
if callback is not None:
|
|
478
|
+
callback(self._hilda, frame, bp_loc, self[bp_id])
|
|
479
|
+
finally:
|
|
480
|
+
self._hilda._bp_frame = None
|