hilda 2.0.16__tar.gz → 3.0.1__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.
Files changed (81) hide show
  1. {hilda-2.0.16 → hilda-3.0.1}/.pre-commit-config.yaml +1 -1
  2. {hilda-2.0.16 → hilda-3.0.1}/PKG-INFO +15 -15
  3. {hilda-2.0.16 → hilda-3.0.1}/README.md +13 -14
  4. {hilda-2.0.16 → hilda-3.0.1}/hilda/_version.py +9 -4
  5. hilda-3.0.1/hilda/breakpoints.py +480 -0
  6. {hilda-2.0.16 → hilda-3.0.1}/hilda/hilda_client.py +80 -305
  7. hilda-3.0.1/hilda/ipython_extensions/keybindings.py +47 -0
  8. {hilda-2.0.16 → hilda-3.0.1}/hilda/launch_lldb.py +29 -12
  9. {hilda-2.0.16 → hilda-3.0.1}/hilda/objective_c_class.py +2 -6
  10. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/mach/CFRunLoopServiceMachPort_hooks.py +2 -5
  11. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/macho/all_image_infos.py +7 -4
  12. {hilda-2.0.16 → hilda-3.0.1}/hilda/symbol.py +3 -0
  13. hilda-3.0.1/hilda/watchpoints.py +237 -0
  14. {hilda-2.0.16 → hilda-3.0.1}/hilda.egg-info/PKG-INFO +15 -15
  15. {hilda-2.0.16 → hilda-3.0.1}/hilda.egg-info/SOURCES.txt +2 -0
  16. {hilda-2.0.16 → hilda-3.0.1}/hilda.egg-info/requires.txt +1 -0
  17. {hilda-2.0.16 → hilda-3.0.1}/requirements.txt +2 -1
  18. {hilda-2.0.16 → hilda-3.0.1}/tests/test_hilda_client/test_monitor.py +1 -1
  19. hilda-2.0.16/hilda/ipython_extensions/keybindings.py +0 -24
  20. {hilda-2.0.16 → hilda-3.0.1}/.github/workflows/python-app.yml +0 -0
  21. {hilda-2.0.16 → hilda-3.0.1}/.github/workflows/python-publish.yml +0 -0
  22. {hilda-2.0.16 → hilda-3.0.1}/.gitignore +0 -0
  23. {hilda-2.0.16 → hilda-3.0.1}/LICENSE +0 -0
  24. {hilda-2.0.16 → hilda-3.0.1}/gifs/.gitattributes +0 -0
  25. {hilda-2.0.16 → hilda-3.0.1}/gifs/ui.png +0 -0
  26. {hilda-2.0.16 → hilda-3.0.1}/gifs/xpc_print_message.gif +0 -0
  27. {hilda-2.0.16 → hilda-3.0.1}/hilda/__init__.py +0 -0
  28. {hilda-2.0.16 → hilda-3.0.1}/hilda/__main__.py +0 -0
  29. {hilda-2.0.16 → hilda-3.0.1}/hilda/cli.py +0 -0
  30. {hilda-2.0.16 → hilda-3.0.1}/hilda/common.py +0 -0
  31. {hilda-2.0.16 → hilda-3.0.1}/hilda/exceptions.py +0 -0
  32. {hilda-2.0.16 → hilda-3.0.1}/hilda/hilda_ascii_art.html +0 -0
  33. {hilda-2.0.16 → hilda-3.0.1}/hilda/ipython_extensions/events.py +0 -0
  34. {hilda-2.0.16 → hilda-3.0.1}/hilda/ipython_extensions/magics.py +0 -0
  35. {hilda-2.0.16 → hilda-3.0.1}/hilda/lldb_entrypoint.py +0 -0
  36. {hilda-2.0.16 → hilda-3.0.1}/hilda/lldb_importer.py +0 -0
  37. {hilda-2.0.16 → hilda-3.0.1}/hilda/objective_c/from_ns_to_json.m +0 -0
  38. {hilda-2.0.16 → hilda-3.0.1}/hilda/objective_c/get_objectivec_class_by_module.m +0 -0
  39. {hilda-2.0.16 → hilda-3.0.1}/hilda/objective_c/get_objectivec_class_description.m +0 -0
  40. {hilda-2.0.16 → hilda-3.0.1}/hilda/objective_c/get_objectivec_symbol_data.m +0 -0
  41. {hilda-2.0.16 → hilda-3.0.1}/hilda/objective_c/lsof.m +0 -0
  42. {hilda-2.0.16 → hilda-3.0.1}/hilda/objective_c/to_ns_from_json.m +0 -0
  43. {hilda-2.0.16 → hilda-3.0.1}/hilda/objective_c_symbol.py +0 -0
  44. {hilda-2.0.16 → hilda-3.0.1}/hilda/registers.py +0 -0
  45. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/__init__.py +0 -0
  46. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/boringssl.py +0 -0
  47. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/collections.py +0 -0
  48. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/dyld.py +0 -0
  49. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/fs_utils.py +0 -0
  50. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/libmalloc.py +0 -0
  51. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/mach/__init__.py +0 -0
  52. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/macho/__init__.py +0 -0
  53. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/macho/apple_version.py +0 -0
  54. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/macho/image_info.py +0 -0
  55. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/macho/macho.py +0 -0
  56. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/macho/macho_load_commands.py +0 -0
  57. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/remotepairingd.py +0 -0
  58. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/syslog.py +0 -0
  59. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/uuid.py +0 -0
  60. {hilda-2.0.16 → hilda-3.0.1}/hilda/snippets/xpc.py +0 -0
  61. {hilda-2.0.16 → hilda-3.0.1}/hilda/symbols_jar.py +0 -0
  62. {hilda-2.0.16 → hilda-3.0.1}/hilda/ui/colors.json +0 -0
  63. {hilda-2.0.16 → hilda-3.0.1}/hilda/ui/ui_manager.py +0 -0
  64. {hilda-2.0.16 → hilda-3.0.1}/hilda/ui/views.py +0 -0
  65. {hilda-2.0.16 → hilda-3.0.1}/hilda.egg-info/dependency_links.txt +0 -0
  66. {hilda-2.0.16 → hilda-3.0.1}/hilda.egg-info/entry_points.txt +0 -0
  67. {hilda-2.0.16 → hilda-3.0.1}/hilda.egg-info/top_level.txt +0 -0
  68. {hilda-2.0.16 → hilda-3.0.1}/pyproject.toml +0 -0
  69. {hilda-2.0.16 → hilda-3.0.1}/setup.cfg +0 -0
  70. {hilda-2.0.16 → hilda-3.0.1}/tests/__init__.py +0 -0
  71. {hilda-2.0.16 → hilda-3.0.1}/tests/conftest.py +0 -0
  72. {hilda-2.0.16 → hilda-3.0.1}/tests/test_hilda_client/test_from_ns.py +0 -0
  73. {hilda-2.0.16 → hilda-3.0.1}/tests/test_hilda_client/test_hilda_client.py +0 -0
  74. {hilda-2.0.16 → hilda-3.0.1}/tests/test_hilda_client/test_ns.py +0 -0
  75. {hilda-2.0.16 → hilda-3.0.1}/tests/test_hilda_client/test_rebind_symbols.py +0 -0
  76. {hilda-2.0.16 → hilda-3.0.1}/tests/test_hilda_client/test_registers.py +0 -0
  77. {hilda-2.0.16 → hilda-3.0.1}/tests/test_snippets/test_xpc.py +0 -0
  78. {hilda-2.0.16 → hilda-3.0.1}/tests/test_symbols/test_objective_c_class.py +0 -0
  79. {hilda-2.0.16 → hilda-3.0.1}/tests/test_symbols/test_objective_c_symbol.py +0 -0
  80. {hilda-2.0.16 → hilda-3.0.1}/tests/test_symbols/test_symbol.py +0 -0
  81. {hilda-2.0.16 → hilda-3.0.1}/tests/test_symbols/test_symbols_jar.py +0 -0
@@ -11,5 +11,5 @@ repos:
11
11
  rev: "7.0.0"
12
12
  hooks:
13
13
  - id: flake8
14
- args: [ '--max-complexity=14', '--max-line-length=127' ]
14
+ args: [ '--max-complexity=18', '--max-line-length=127' ]
15
15
  files: \.py$
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: hilda
3
- Version: 2.0.16
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>
@@ -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 an hexdump of given buffer
166
+ - Print a hexdump of given buffer
166
167
  - `lsof`
167
168
  - Get dictionary of all open FDs
168
169
  - `bt`
@@ -209,7 +210,7 @@ Here is a gist of methods you can access from `p`:
209
210
  - Simulate a call to an objc selector
210
211
  - `call`
211
212
  - Call function at given address with given parameters
212
- - `monitor`
213
+ - `monitor` or `breakpoints.add_monitor`
213
214
  - Monitor every time a given address is called
214
215
 
215
216
  The following options are available:
@@ -254,10 +255,10 @@ Here is a gist of methods you can access from `p`:
254
255
  - Step into current instruction.
255
256
  - `step_over`
256
257
  - Step over current instruction.
257
- - `remove_all_hilda_breakpoints`
258
- - Remove all breakpoints created by Hilda
259
- - `remove_hilda_breakpoint`
260
- - Remove a single breakpoint placed by Hilda
258
+ - `breakpoints.clear`
259
+ - Remove all breakpoints
260
+ - `breakpoints.remove`
261
+ - Remove a single breakpoint
261
262
  - `force_return`
262
263
  - Prematurely return from a stack frame, short-circuiting exection of newer frames and optionally
263
264
  yielding a specified value.
@@ -265,14 +266,10 @@ Here is a gist of methods you can access from `p`:
265
266
  - Print information about currently running mapped process.
266
267
  - `print_proc_entitlements`
267
268
  - Get the plist embedded inside the process' __LINKEDIT section.
268
- - `bp`
269
+ - `bp` or `breakpoints.add`
269
270
  - Add a breakpoint
270
- - `show_hilda_breakpoints`
271
- - Show existing breakpoints created by Hilda.
272
- - `save`
273
- - Save loaded symbols map (for loading later using the load() command)
274
- - `load`
275
- - Load an existing symbols map (previously saved by the save() command)
271
+ - `breakpoints.show`
272
+ - Show existing breakpoints
276
273
  - `po`
277
274
  - Print given object using LLDB's po command
278
275
  Can also run big chunks of native code:
@@ -326,6 +323,9 @@ Sometimes accessing the [Python API](#python-api) can be tiring, so we added som
326
323
 
327
324
  #### Key-bindings
328
325
 
326
+ - **F1**: Show banner help message
327
+ - **F2**: Show process state UI
328
+ - **F3**: Toggle stdout/stderr enablement
329
329
  - **F7**: Step Into
330
330
  - **F8**: Step Over
331
331
  - **F9**: Continue
@@ -531,7 +531,7 @@ s.bp(scripted_breakpoint)
531
531
  p.bp('symbol_name')
532
532
 
533
533
  # In case you need to specify a specific library it's loaded from
534
- p.bp('symbol_name', module_name='ModuleName')
534
+ p.bp(('symbol_name', 'ModuleName'))
535
535
  ```
536
536
 
537
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 an hexdump of given buffer
108
+ - Print a hexdump of given buffer
109
109
  - `lsof`
110
110
  - Get dictionary of all open FDs
111
111
  - `bt`
@@ -152,7 +152,7 @@ Here is a gist of methods you can access from `p`:
152
152
  - Simulate a call to an objc selector
153
153
  - `call`
154
154
  - Call function at given address with given parameters
155
- - `monitor`
155
+ - `monitor` or `breakpoints.add_monitor`
156
156
  - Monitor every time a given address is called
157
157
 
158
158
  The following options are available:
@@ -197,10 +197,10 @@ Here is a gist of methods you can access from `p`:
197
197
  - Step into current instruction.
198
198
  - `step_over`
199
199
  - Step over current instruction.
200
- - `remove_all_hilda_breakpoints`
201
- - Remove all breakpoints created by Hilda
202
- - `remove_hilda_breakpoint`
203
- - Remove a single breakpoint placed by Hilda
200
+ - `breakpoints.clear`
201
+ - Remove all breakpoints
202
+ - `breakpoints.remove`
203
+ - Remove a single breakpoint
204
204
  - `force_return`
205
205
  - Prematurely return from a stack frame, short-circuiting exection of newer frames and optionally
206
206
  yielding a specified value.
@@ -208,14 +208,10 @@ Here is a gist of methods you can access from `p`:
208
208
  - Print information about currently running mapped process.
209
209
  - `print_proc_entitlements`
210
210
  - Get the plist embedded inside the process' __LINKEDIT section.
211
- - `bp`
211
+ - `bp` or `breakpoints.add`
212
212
  - Add a breakpoint
213
- - `show_hilda_breakpoints`
214
- - Show existing breakpoints created by Hilda.
215
- - `save`
216
- - Save loaded symbols map (for loading later using the load() command)
217
- - `load`
218
- - Load an existing symbols map (previously saved by the save() command)
213
+ - `breakpoints.show`
214
+ - Show existing breakpoints
219
215
  - `po`
220
216
  - Print given object using LLDB's po command
221
217
  Can also run big chunks of native code:
@@ -269,6 +265,9 @@ Sometimes accessing the [Python API](#python-api) can be tiring, so we added som
269
265
 
270
266
  #### Key-bindings
271
267
 
268
+ - **F1**: Show banner help message
269
+ - **F2**: Show process state UI
270
+ - **F3**: Toggle stdout/stderr enablement
272
271
  - **F7**: Step Into
273
272
  - **F8**: Step Over
274
273
  - **F9**: Continue
@@ -474,7 +473,7 @@ s.bp(scripted_breakpoint)
474
473
  p.bp('symbol_name')
475
474
 
476
475
  # In case you need to specify a specific library it's loaded from
477
- p.bp('symbol_name', module_name='ModuleName')
476
+ p.bp(('symbol_name', 'ModuleName'))
478
477
  ```
479
478
 
480
479
  #### Globalized symbols
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
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, Union
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 = '2.0.16'
16
- __version_tuple__ = version_tuple = (2, 0, 16)
20
+ __version__ = version = '3.0.1'
21
+ __version_tuple__ = version_tuple = (3, 0, 1)
@@ -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