pymobiledevice3 4.27.4__py3-none-any.whl → 5.1.2__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.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- pymobiledevice3/__main__.py +123 -98
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +351 -117
- pymobiledevice3/ca.py +32 -24
- pymobiledevice3/cli/activation.py +7 -7
- pymobiledevice3/cli/afc.py +19 -19
- pymobiledevice3/cli/amfi.py +4 -4
- pymobiledevice3/cli/apps.py +51 -39
- pymobiledevice3/cli/backup.py +58 -32
- pymobiledevice3/cli/bonjour.py +27 -20
- pymobiledevice3/cli/cli_common.py +112 -81
- pymobiledevice3/cli/companion_proxy.py +4 -4
- pymobiledevice3/cli/completions.py +10 -10
- pymobiledevice3/cli/crash.py +37 -31
- pymobiledevice3/cli/developer.py +601 -519
- pymobiledevice3/cli/diagnostics.py +38 -33
- pymobiledevice3/cli/lockdown.py +82 -72
- pymobiledevice3/cli/mounter.py +84 -67
- pymobiledevice3/cli/notification.py +10 -10
- pymobiledevice3/cli/pcap.py +19 -14
- pymobiledevice3/cli/power_assertion.py +12 -10
- pymobiledevice3/cli/processes.py +10 -10
- pymobiledevice3/cli/profile.py +88 -77
- pymobiledevice3/cli/provision.py +17 -17
- pymobiledevice3/cli/remote.py +188 -111
- pymobiledevice3/cli/restore.py +43 -40
- pymobiledevice3/cli/springboard.py +30 -28
- pymobiledevice3/cli/syslog.py +85 -58
- pymobiledevice3/cli/usbmux.py +21 -20
- pymobiledevice3/cli/version.py +3 -2
- pymobiledevice3/cli/webinspector.py +156 -78
- pymobiledevice3/common.py +1 -1
- pymobiledevice3/exceptions.py +154 -60
- pymobiledevice3/irecv.py +49 -53
- pymobiledevice3/irecv_devices.py +1489 -492
- pymobiledevice3/lockdown.py +400 -251
- pymobiledevice3/lockdown_service_provider.py +5 -7
- pymobiledevice3/osu/os_utils.py +18 -9
- pymobiledevice3/osu/posix_util.py +28 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +19 -19
- pymobiledevice3/remote/common.py +4 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +10 -8
- pymobiledevice3/remote/core_device/file_service.py +47 -33
- pymobiledevice3/remote/remote_service_discovery.py +53 -35
- pymobiledevice3/remote/remotexpc.py +64 -42
- pymobiledevice3/remote/tunnel_service.py +383 -297
- pymobiledevice3/remote/utils.py +14 -13
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +16 -16
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +90 -47
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +11 -11
- pymobiledevice3/restore/fdr.py +46 -46
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +130 -133
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +125 -135
- pymobiledevice3/restore/restore.py +535 -523
- pymobiledevice3/restore/restore_options.py +122 -115
- pymobiledevice3/restore/restored_client.py +25 -22
- pymobiledevice3/restore/tss.py +378 -270
- pymobiledevice3/service_connection.py +50 -46
- pymobiledevice3/services/accessibilityaudit.py +137 -127
- pymobiledevice3/services/afc.py +352 -292
- pymobiledevice3/services/amfi.py +21 -18
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +61 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +56 -48
- pymobiledevice3/services/diagnostics.py +971 -968
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +11 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +25 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +8 -7
- pymobiledevice3/services/house_arrest.py +12 -15
- pymobiledevice3/services/installation_proxy.py +119 -100
- pymobiledevice3/services/lockdown_service.py +12 -5
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +84 -72
- pymobiledevice3/services/mobile_config.py +331 -301
- pymobiledevice3/services/mobile_image_mounter.py +137 -116
- pymobiledevice3/services/mobilebackup2.py +188 -150
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +128 -74
- pymobiledevice3/services/pcapd.py +306 -306
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +16 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +13 -10
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +3 -3
- pymobiledevice3/services/web_protocol/automation_session.py +183 -179
- pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +47 -45
- pymobiledevice3/services/web_protocol/element.py +74 -63
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +3 -3
- pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
- pymobiledevice3/services/web_protocol/switch_to.py +11 -12
- pymobiledevice3/services/webinspector.py +142 -116
- pymobiledevice3/tcp_forwarder.py +35 -22
- pymobiledevice3/tunneld/api.py +20 -15
- pymobiledevice3/tunneld/server.py +310 -193
- pymobiledevice3/usbmux.py +197 -148
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/METADATA +1 -2
- pymobiledevice3-5.1.2.dist-info/RECORD +173 -0
- pymobiledevice3-4.27.4.dist-info/RECORD +0 -172
- {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/WHEEL +0 -0
- {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/top_level.txt +0 -0
pymobiledevice3/cli/developer.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
# flake8: noqa: C901
|
|
2
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
@@ -24,10 +24,21 @@ from plumbum import local
|
|
|
24
24
|
from pykdebugparser.pykdebugparser import PyKdebugParser
|
|
25
25
|
|
|
26
26
|
import pymobiledevice3
|
|
27
|
-
from pymobiledevice3.cli.cli_common import
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
from pymobiledevice3.cli.cli_common import (
|
|
28
|
+
BASED_INT,
|
|
29
|
+
Command,
|
|
30
|
+
RSDCommand,
|
|
31
|
+
default_json_encoder,
|
|
32
|
+
print_json,
|
|
33
|
+
user_requested_colored_output,
|
|
34
|
+
)
|
|
35
|
+
from pymobiledevice3.exceptions import (
|
|
36
|
+
DeviceAlreadyInUseError,
|
|
37
|
+
DvtDirListError,
|
|
38
|
+
ExtractingStackshotError,
|
|
39
|
+
RSDRequiredError,
|
|
40
|
+
UnrecognizedSelectorError,
|
|
41
|
+
)
|
|
31
42
|
from pymobiledevice3.lockdown import LockdownClient, create_using_usbmux
|
|
32
43
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
33
44
|
from pymobiledevice3.osu.os_utils import get_os_utils
|
|
@@ -65,10 +76,10 @@ from pymobiledevice3.tcp_forwarder import LockdownTcpForwarder
|
|
|
65
76
|
from pymobiledevice3.utils import try_decode
|
|
66
77
|
|
|
67
78
|
OSUTILS = get_os_utils()
|
|
68
|
-
BSC_SUBCLASS =
|
|
79
|
+
BSC_SUBCLASS = 0x40C
|
|
69
80
|
BSC_CLASS = 0x4
|
|
70
|
-
VFS_AND_TRACES_SET = {0x03010000,
|
|
71
|
-
DEBUGSERVER_CONNECTION_STEPS =
|
|
81
|
+
VFS_AND_TRACES_SET = {0x03010000, 0x07FF0000}
|
|
82
|
+
DEBUGSERVER_CONNECTION_STEPS = """
|
|
72
83
|
Follow the following connections steps from LLDB:
|
|
73
84
|
|
|
74
85
|
(lldb) platform select remote-ios
|
|
@@ -76,9 +87,9 @@ Follow the following connections steps from LLDB:
|
|
|
76
87
|
(lldb) script lldb.target.module[0].SetPlatformFileSpec(lldb.SBFileSpec('/private/var/containers/Bundle/Application/<APP-UUID>/application.app'))
|
|
77
88
|
(lldb) process connect connect://[{host}]:{port} <-- ACTUAL CONNECTION DETAILS!
|
|
78
89
|
(lldb) process launch
|
|
79
|
-
|
|
90
|
+
"""
|
|
80
91
|
|
|
81
|
-
MatchedProcessByPid = namedtuple(
|
|
92
|
+
MatchedProcessByPid = namedtuple("MatchedProcess", "name pid")
|
|
82
93
|
|
|
83
94
|
logger = logging.getLogger(__name__)
|
|
84
95
|
|
|
@@ -105,148 +116,160 @@ def developer() -> None:
|
|
|
105
116
|
pass
|
|
106
117
|
|
|
107
118
|
|
|
108
|
-
@developer.command(
|
|
109
|
-
@click.argument(
|
|
110
|
-
@click.option(
|
|
119
|
+
@developer.command("shell", cls=Command)
|
|
120
|
+
@click.argument("service")
|
|
121
|
+
@click.option("-r", "--remove-ssl-context", is_flag=True)
|
|
111
122
|
def developer_shell(service_provider: LockdownClient, service, remove_ssl_context):
|
|
112
|
-
"""
|
|
123
|
+
"""Launch developer IPython shell (used for pymobiledevice3 R&D)"""
|
|
113
124
|
with RemoteServer(service_provider, service, remove_ssl_context) as service:
|
|
114
125
|
service.shell()
|
|
115
126
|
|
|
116
127
|
|
|
117
128
|
@developer.group()
|
|
118
129
|
def dvt() -> None:
|
|
119
|
-
"""
|
|
130
|
+
"""Access advanced instrumentation APIs"""
|
|
120
131
|
pass
|
|
121
132
|
|
|
122
133
|
|
|
123
|
-
@dvt.command(
|
|
134
|
+
@dvt.command("proclist", cls=Command)
|
|
124
135
|
def proclist(service_provider: LockdownClient) -> None:
|
|
125
|
-
"""
|
|
136
|
+
"""Show process list"""
|
|
126
137
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
127
138
|
processes = DeviceInfo(dvt).proclist()
|
|
128
139
|
for process in processes:
|
|
129
|
-
if
|
|
130
|
-
process[
|
|
140
|
+
if "startDate" in process:
|
|
141
|
+
process["startDate"] = str(process["startDate"])
|
|
131
142
|
|
|
132
143
|
print_json(processes)
|
|
133
144
|
|
|
134
145
|
|
|
135
|
-
@dvt.command(
|
|
136
|
-
@click.argument(
|
|
146
|
+
@dvt.command("is-running-pid", cls=Command)
|
|
147
|
+
@click.argument("pid", type=click.INT)
|
|
137
148
|
def is_running_pid(service_provider: LockdownClient, pid: int) -> None:
|
|
138
|
-
"""
|
|
149
|
+
"""Simple check if PID is running"""
|
|
139
150
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
140
151
|
print_json(DeviceInfo(dvt).is_running_pid(pid))
|
|
141
152
|
|
|
142
153
|
|
|
143
|
-
@dvt.command(
|
|
144
|
-
@click.argument(
|
|
154
|
+
@dvt.command("memlimitoff", cls=Command)
|
|
155
|
+
@click.argument("pid", type=click.INT)
|
|
145
156
|
def memlimitoff(service_provider: LockdownServiceProvider, pid: int) -> None:
|
|
146
|
-
"""
|
|
157
|
+
"""Disable process memory limit"""
|
|
147
158
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
148
159
|
ProcessControl(dvt).disable_memory_limit_for_pid(pid)
|
|
149
160
|
|
|
150
161
|
|
|
151
|
-
@dvt.command(
|
|
162
|
+
@dvt.command("applist", cls=Command)
|
|
152
163
|
def applist(service_provider: LockdownServiceProvider) -> None:
|
|
153
|
-
"""
|
|
164
|
+
"""Show application list"""
|
|
154
165
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
155
166
|
apps = ApplicationListing(dvt).applist()
|
|
156
167
|
print_json(apps)
|
|
157
168
|
|
|
158
169
|
|
|
159
|
-
@dvt.command(
|
|
160
|
-
@click.argument(
|
|
161
|
-
@click.argument(
|
|
162
|
-
@click.option(
|
|
170
|
+
@dvt.command("signal", cls=Command)
|
|
171
|
+
@click.argument("pid", type=click.INT)
|
|
172
|
+
@click.argument("sig", type=click.INT, required=False)
|
|
173
|
+
@click.option("-s", "--signal-name", type=click.Choice([s.name for s in signal.Signals]))
|
|
163
174
|
def send_signal(service_provider, pid, sig, signal_name) -> None:
|
|
164
|
-
"""
|
|
175
|
+
"""Send a signal to process by its PID"""
|
|
165
176
|
if not sig and not signal_name:
|
|
166
|
-
raise MissingParameter(param_type=
|
|
177
|
+
raise MissingParameter(param_type="argument|option", param_hint="'SIG|SIGNAL-NAME'")
|
|
167
178
|
if sig and signal_name:
|
|
168
|
-
raise UsageError(message=
|
|
179
|
+
raise UsageError(message="Cannot give SIG and SIGNAL-NAME together")
|
|
169
180
|
sig = sig or signal.Signals[signal_name].value
|
|
170
181
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
171
182
|
ProcessControl(dvt).signal(pid, sig)
|
|
172
183
|
|
|
173
184
|
|
|
174
|
-
@dvt.command(
|
|
175
|
-
@click.argument(
|
|
185
|
+
@dvt.command("kill", cls=Command)
|
|
186
|
+
@click.argument("pid", type=click.INT)
|
|
176
187
|
def kill(service_provider: LockdownClient, pid) -> None:
|
|
177
|
-
"""
|
|
188
|
+
"""Kill a process by its pid."""
|
|
178
189
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
179
190
|
ProcessControl(dvt).kill(pid)
|
|
180
191
|
|
|
181
192
|
|
|
182
193
|
@dvt.command(cls=Command)
|
|
183
|
-
@click.argument(
|
|
194
|
+
@click.argument("app_bundle_identifier")
|
|
184
195
|
def process_id_for_bundle_id(service_provider: LockdownServiceProvider, app_bundle_identifier: str) -> None:
|
|
185
|
-
"""
|
|
196
|
+
"""Get PID of a bundle identifier (only returns a valid value if its running)."""
|
|
186
197
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
187
198
|
print(ProcessControl(dvt).process_identifier_for_bundle_identifier(app_bundle_identifier))
|
|
188
199
|
|
|
189
200
|
|
|
190
|
-
def get_matching_processes(
|
|
191
|
-
|
|
192
|
-
|
|
201
|
+
def get_matching_processes(
|
|
202
|
+
service_provider: LockdownServiceProvider, name: Optional[str] = None, bundle_identifier: Optional[str] = None
|
|
203
|
+
) -> list[MatchedProcessByPid]:
|
|
193
204
|
result = []
|
|
194
205
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
195
206
|
device_info = DeviceInfo(dvt)
|
|
196
207
|
for process in device_info.proclist():
|
|
197
|
-
current_name = process.get(
|
|
198
|
-
current_bundle_identifier = process.get(
|
|
199
|
-
pid = process[
|
|
200
|
-
if (bundle_identifier is not None and bundle_identifier in current_bundle_identifier) or
|
|
201
|
-
|
|
208
|
+
current_name = process.get("name")
|
|
209
|
+
current_bundle_identifier = process.get("bundleIdentifier", "")
|
|
210
|
+
pid = process["pid"]
|
|
211
|
+
if (bundle_identifier is not None and bundle_identifier in current_bundle_identifier) or (
|
|
212
|
+
name is not None and name in current_name
|
|
213
|
+
):
|
|
202
214
|
result.append(MatchedProcessByPid(name=current_name, pid=pid))
|
|
203
215
|
return result
|
|
204
216
|
|
|
205
217
|
|
|
206
|
-
@dvt.command(
|
|
207
|
-
@click.argument(
|
|
208
|
-
@click.option(
|
|
218
|
+
@dvt.command("pkill", cls=Command)
|
|
219
|
+
@click.argument("expression")
|
|
220
|
+
@click.option("--bundle", is_flag=True, help="Treat given expression as a bundle-identifier instead of a process name")
|
|
209
221
|
def pkill(service_provider: LockdownServiceProvider, expression: str, bundle: False) -> None:
|
|
210
|
-
"""
|
|
222
|
+
"""kill all processes containing `expression` in their name."""
|
|
211
223
|
matching_name = expression if not bundle else None
|
|
212
224
|
matching_bundle_identifier = expression if bundle else None
|
|
213
|
-
matching_processes = get_matching_processes(
|
|
214
|
-
|
|
225
|
+
matching_processes = get_matching_processes(
|
|
226
|
+
service_provider, name=matching_name, bundle_identifier=matching_bundle_identifier
|
|
227
|
+
)
|
|
215
228
|
|
|
216
229
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
217
230
|
process_control = ProcessControl(dvt)
|
|
218
231
|
|
|
219
232
|
for process in matching_processes:
|
|
220
|
-
logger.info(f
|
|
233
|
+
logger.info(f"killing {process.name}({process.pid})")
|
|
221
234
|
process_control.kill(process.pid)
|
|
222
235
|
|
|
223
236
|
|
|
224
|
-
@dvt.command(
|
|
225
|
-
@click.argument(
|
|
226
|
-
@click.option(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
@click.option(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
"
|
|
237
|
+
@dvt.command("launch", cls=Command)
|
|
238
|
+
@click.argument("arguments", type=click.STRING)
|
|
239
|
+
@click.option(
|
|
240
|
+
"--kill-existing/--no-kill-existing", default=True, help="Whether to kill an existing instance of this process"
|
|
241
|
+
)
|
|
242
|
+
@click.option("--suspended", is_flag=True, help="Same as WaitForDebugger")
|
|
243
|
+
@click.option(
|
|
244
|
+
"--env",
|
|
245
|
+
multiple=True,
|
|
246
|
+
type=click.Tuple((str, str)),
|
|
247
|
+
help="Environment variables to pass to process given as a list of key value",
|
|
248
|
+
)
|
|
249
|
+
@click.option("--stream", is_flag=True)
|
|
250
|
+
def launch(
|
|
251
|
+
service_provider: LockdownClient, arguments: str, kill_existing: bool, suspended: bool, env: tuple, stream: bool
|
|
252
|
+
) -> None:
|
|
253
|
+
"""Launch a process."""
|
|
235
254
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
236
255
|
parsed_arguments = shlex.split(arguments)
|
|
237
256
|
process_control = ProcessControl(dvt)
|
|
238
|
-
pid = process_control.launch(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
257
|
+
pid = process_control.launch(
|
|
258
|
+
bundle_id=parsed_arguments[0],
|
|
259
|
+
arguments=parsed_arguments[1:],
|
|
260
|
+
kill_existing=kill_existing,
|
|
261
|
+
start_suspended=suspended,
|
|
262
|
+
environment=dict(env),
|
|
263
|
+
)
|
|
264
|
+
print(f"Process launched with pid {pid}")
|
|
242
265
|
while stream:
|
|
243
266
|
for output_received in process_control:
|
|
244
|
-
logging.getLogger(f
|
|
267
|
+
logging.getLogger(f"PID:{output_received.pid}").info(output_received.message.strip())
|
|
245
268
|
|
|
246
269
|
|
|
247
|
-
@dvt.command(
|
|
270
|
+
@dvt.command("shell", cls=Command)
|
|
248
271
|
def dvt_shell(service_provider: LockdownClient):
|
|
249
|
-
"""
|
|
272
|
+
"""Launch developer shell (used for pymobiledevice3 R&D)"""
|
|
250
273
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
251
274
|
dvt.shell()
|
|
252
275
|
|
|
@@ -264,55 +287,53 @@ def show_dirlist(device_info: DeviceInfo, dirname, recursive=False):
|
|
|
264
287
|
show_dirlist(device_info, filename, recursive=recursive)
|
|
265
288
|
|
|
266
289
|
|
|
267
|
-
@dvt.command(
|
|
268
|
-
@click.argument(
|
|
269
|
-
@click.option(
|
|
290
|
+
@dvt.command("ls", cls=Command)
|
|
291
|
+
@click.argument("path", type=click.Path(exists=False, readable=False))
|
|
292
|
+
@click.option("-r", "--recursive", is_flag=True)
|
|
270
293
|
def ls(service_provider: LockdownClient, path, recursive):
|
|
271
|
-
"""
|
|
294
|
+
"""List directory"""
|
|
272
295
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
273
296
|
show_dirlist(DeviceInfo(dvt), path, recursive=recursive)
|
|
274
297
|
|
|
275
298
|
|
|
276
|
-
@dvt.command(
|
|
299
|
+
@dvt.command("device-information", cls=Command)
|
|
277
300
|
def device_information(service_provider: LockdownClient):
|
|
278
|
-
"""
|
|
301
|
+
"""Print system information"""
|
|
279
302
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
280
303
|
device_info = DeviceInfo(dvt)
|
|
281
304
|
info = {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
305
|
+
"hardware": device_info.hardware_information(),
|
|
306
|
+
"network": device_info.network_information(),
|
|
307
|
+
"kernel-name": device_info.mach_kernel_name(),
|
|
308
|
+
"kpep-database": device_info.kpep_database(),
|
|
286
309
|
}
|
|
287
|
-
|
|
288
|
-
info[
|
|
289
|
-
except UnrecognizedSelectorError:
|
|
290
|
-
pass
|
|
310
|
+
with contextlib.suppress(UnrecognizedSelectorError):
|
|
311
|
+
info["system"] = device_info.system_information()
|
|
291
312
|
print_json(info)
|
|
292
313
|
|
|
293
314
|
|
|
294
|
-
@dvt.command(
|
|
315
|
+
@dvt.command("netstat", cls=Command)
|
|
295
316
|
def netstat(service_provider: LockdownClient):
|
|
296
|
-
"""
|
|
297
|
-
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
@dvt.command(
|
|
307
|
-
@click.argument(
|
|
308
|
-
def
|
|
309
|
-
"""
|
|
317
|
+
"""Print information about current network activity."""
|
|
318
|
+
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, NetworkMonitor(dvt) as monitor:
|
|
319
|
+
for event in monitor:
|
|
320
|
+
if isinstance(event, ConnectionDetectionEvent):
|
|
321
|
+
logger.info(
|
|
322
|
+
f"Connection detected: {event.local_address.data.address}:{event.local_address.port} -> "
|
|
323
|
+
f"{event.remote_address.data.address}:{event.remote_address.port}"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@dvt.command("screenshot", cls=Command)
|
|
328
|
+
@click.argument("out", type=click.File("wb"))
|
|
329
|
+
def dvt_screenshot(service_provider: LockdownClient, out):
|
|
330
|
+
"""Take device screenshot"""
|
|
310
331
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
311
332
|
out.write(Screenshot(dvt).get_screenshot())
|
|
312
333
|
|
|
313
334
|
|
|
314
|
-
@dvt.command(
|
|
315
|
-
@click.argument(
|
|
335
|
+
@dvt.command("xcuitest", cls=Command)
|
|
336
|
+
@click.argument("bundle-id")
|
|
316
337
|
def xcuitest(service_provider: LockdownClient, bundle_id: str) -> None:
|
|
317
338
|
"""\b
|
|
318
339
|
Start XCUITest
|
|
@@ -323,40 +344,44 @@ def xcuitest(service_provider: LockdownClient, bundle_id: str) -> None:
|
|
|
323
344
|
XCUITestService(service_provider).run(bundle_id)
|
|
324
345
|
|
|
325
346
|
|
|
326
|
-
@dvt.group(
|
|
347
|
+
@dvt.group("sysmon")
|
|
327
348
|
def sysmon():
|
|
328
|
-
"""
|
|
349
|
+
"""System monitor options."""
|
|
329
350
|
|
|
330
351
|
|
|
331
|
-
@sysmon.group(
|
|
352
|
+
@sysmon.group("process")
|
|
332
353
|
def sysmon_process():
|
|
333
|
-
"""
|
|
354
|
+
"""Process monitor options."""
|
|
334
355
|
|
|
335
356
|
|
|
336
|
-
@sysmon_process.command(
|
|
337
|
-
@click.argument(
|
|
357
|
+
@sysmon_process.command("monitor", cls=Command)
|
|
358
|
+
@click.argument("threshold", type=click.FLOAT)
|
|
338
359
|
def sysmon_process_monitor(service_provider: LockdownClient, threshold):
|
|
339
|
-
"""
|
|
360
|
+
"""monitor all most consuming processes by given cpuUsage threshold."""
|
|
340
361
|
|
|
341
|
-
Process = namedtuple(
|
|
362
|
+
Process = namedtuple("process", "pid name cpuUsage physFootprint")
|
|
342
363
|
|
|
343
|
-
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
364
|
+
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, Sysmontap(dvt) as sysmon:
|
|
365
|
+
for process_snapshot in sysmon.iter_processes():
|
|
366
|
+
entries = []
|
|
367
|
+
for process in process_snapshot:
|
|
368
|
+
if (process["cpuUsage"] is not None) and (process["cpuUsage"] >= threshold):
|
|
369
|
+
entries.append(
|
|
370
|
+
Process(
|
|
371
|
+
pid=process["pid"],
|
|
372
|
+
name=process["name"],
|
|
373
|
+
cpuUsage=process["cpuUsage"],
|
|
374
|
+
physFootprint=process["physFootprint"],
|
|
375
|
+
)
|
|
376
|
+
)
|
|
351
377
|
|
|
352
|
-
|
|
378
|
+
logger.info(entries)
|
|
353
379
|
|
|
354
380
|
|
|
355
|
-
@sysmon_process.command(
|
|
356
|
-
@click.option(
|
|
357
|
-
help='filter processes by given attribute value given as key=value')
|
|
381
|
+
@sysmon_process.command("single", cls=Command)
|
|
382
|
+
@click.option("-a", "--attributes", multiple=True, help="filter processes by given attribute value given as key=value")
|
|
358
383
|
def sysmon_process_single(service_provider: LockdownClient, attributes: list[str]):
|
|
359
|
-
"""
|
|
384
|
+
"""show a single snapshot of currently running processes."""
|
|
360
385
|
|
|
361
386
|
count = 0
|
|
362
387
|
|
|
@@ -376,7 +401,7 @@ def sysmon_process_single(service_provider: LockdownClient, attributes: list[str
|
|
|
376
401
|
skip = False
|
|
377
402
|
if attributes is not None:
|
|
378
403
|
for filter_attr in attributes:
|
|
379
|
-
filter_attr, filter_value = filter_attr.split(
|
|
404
|
+
filter_attr, filter_value = filter_attr.split("=")
|
|
380
405
|
if str(process[filter_attr]) != filter_value:
|
|
381
406
|
skip = True
|
|
382
407
|
break
|
|
@@ -385,7 +410,7 @@ def sysmon_process_single(service_provider: LockdownClient, attributes: list[str
|
|
|
385
410
|
continue
|
|
386
411
|
|
|
387
412
|
# adding "artificially" the execName field
|
|
388
|
-
process[
|
|
413
|
+
process["execName"] = device_info.execname_for_pid(process["pid"])
|
|
389
414
|
result.append(process)
|
|
390
415
|
|
|
391
416
|
# exit after single snapshot
|
|
@@ -393,13 +418,13 @@ def sysmon_process_single(service_provider: LockdownClient, attributes: list[str
|
|
|
393
418
|
print_json(result)
|
|
394
419
|
|
|
395
420
|
|
|
396
|
-
@sysmon.command(
|
|
397
|
-
@click.option(
|
|
421
|
+
@sysmon.command("system", cls=Command)
|
|
422
|
+
@click.option("-f", "--fields", help='field names splitted by ",".')
|
|
398
423
|
def sysmon_system(service_provider: LockdownClient, fields):
|
|
399
|
-
"""
|
|
424
|
+
"""show current system stats."""
|
|
400
425
|
|
|
401
426
|
if fields is not None:
|
|
402
|
-
fields = fields.split(
|
|
427
|
+
fields = fields.split(",")
|
|
403
428
|
|
|
404
429
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
405
430
|
sysmontap = Sysmontap(dvt)
|
|
@@ -409,16 +434,17 @@ def sysmon_system(service_provider: LockdownClient, fields):
|
|
|
409
434
|
system_usage_seen = False # Tracks if the first occurrence of SystemCPUUsage
|
|
410
435
|
|
|
411
436
|
for row in sysmon:
|
|
412
|
-
if
|
|
413
|
-
system = sysmon.system_attributes_cls(*row[
|
|
437
|
+
if "System" in row and system is None:
|
|
438
|
+
system = sysmon.system_attributes_cls(*row["System"])
|
|
414
439
|
|
|
415
|
-
if
|
|
440
|
+
if "SystemCPUUsage" in row:
|
|
416
441
|
if system_usage_seen:
|
|
417
442
|
system_usage = {
|
|
418
|
-
**row[
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
443
|
+
**row["SystemCPUUsage"],
|
|
444
|
+
**{
|
|
445
|
+
"CPUCount": row["CPUCount"],
|
|
446
|
+
"EnabledCPUs": row["EnabledCPUs"],
|
|
447
|
+
},
|
|
422
448
|
}
|
|
423
449
|
else: # Ignore the first occurrence because first occurrence always gives a incorrect value - 100 or 0
|
|
424
450
|
system_usage_seen = True
|
|
@@ -429,19 +455,21 @@ def sysmon_system(service_provider: LockdownClient, fields):
|
|
|
429
455
|
attrs_dict = {**asdict(system), **system_usage}
|
|
430
456
|
for name, value in attrs_dict.items():
|
|
431
457
|
if (fields is None) or (name in fields):
|
|
432
|
-
print(f
|
|
458
|
+
print(f"{name}: {value}")
|
|
433
459
|
|
|
434
460
|
|
|
435
|
-
@dvt.group(
|
|
461
|
+
@dvt.group("core-profile-session")
|
|
436
462
|
def core_profile_session():
|
|
437
|
-
"""
|
|
463
|
+
"""Access tailspin features"""
|
|
438
464
|
|
|
439
465
|
|
|
440
|
-
bsc_filter = click.option(
|
|
441
|
-
class_filter = click.option(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
466
|
+
bsc_filter = click.option("--bsc/--no-bsc", default=False, help="Whether to print BSC events or not.")
|
|
467
|
+
class_filter = click.option(
|
|
468
|
+
"-cf", "--class-filters", multiple=True, type=BASED_INT, help="Events class filter. Omit for all."
|
|
469
|
+
)
|
|
470
|
+
subclass_filter = click.option(
|
|
471
|
+
"-sf", "--subclass-filters", multiple=True, type=BASED_INT, help="Events subclass filter. Omit for all."
|
|
472
|
+
)
|
|
445
473
|
|
|
446
474
|
|
|
447
475
|
def parse_filters(subclasses: list[int], classes: list[int]):
|
|
@@ -455,30 +483,42 @@ def parse_filters(subclasses: list[int], classes: list[int]):
|
|
|
455
483
|
for class_ in classes:
|
|
456
484
|
if class_ == BSC_CLASS:
|
|
457
485
|
parsed |= VFS_AND_TRACES_SET
|
|
458
|
-
parsed.add((class_ << 24) |
|
|
486
|
+
parsed.add((class_ << 24) | 0x00FF0000)
|
|
459
487
|
return parsed
|
|
460
488
|
|
|
461
489
|
|
|
462
|
-
@core_profile_session.command(
|
|
463
|
-
@click.option(
|
|
490
|
+
@core_profile_session.command("live", cls=Command)
|
|
491
|
+
@click.option("-c", "--count", type=click.INT, default=-1, help="Number of events to print. Omit to endless sniff.")
|
|
464
492
|
@bsc_filter
|
|
465
493
|
@class_filter
|
|
466
494
|
@subclass_filter
|
|
467
|
-
@click.option(
|
|
468
|
-
@click.option(
|
|
469
|
-
@click.option(
|
|
470
|
-
@click.option(
|
|
471
|
-
@click.option(
|
|
472
|
-
@click.option(
|
|
473
|
-
@click.option(
|
|
474
|
-
def live_profile_session(
|
|
475
|
-
|
|
476
|
-
|
|
495
|
+
@click.option("--tid", type=click.INT, default=None, help="Thread ID to filter. Omit for all.")
|
|
496
|
+
@click.option("--timestamp/--no-timestamp", default=True, help="Whether to print timestamp or not.")
|
|
497
|
+
@click.option("--event-name/--no-event-name", default=True, help="Whether to print event name or not.")
|
|
498
|
+
@click.option("--func-qual/--no-func-qual", default=True, help="Whether to print function qualifier or not.")
|
|
499
|
+
@click.option("--show-tid/--no-show-tid", default=True, help="Whether to print thread id or not.")
|
|
500
|
+
@click.option("--process-name/--no-process-name", default=True, help="Whether to print process name or not.")
|
|
501
|
+
@click.option("--args/--no-args", default=True, help="Whether to print event arguments or not.")
|
|
502
|
+
def live_profile_session(
|
|
503
|
+
service_provider: LockdownClient,
|
|
504
|
+
count,
|
|
505
|
+
bsc,
|
|
506
|
+
class_filters,
|
|
507
|
+
subclass_filters,
|
|
508
|
+
tid,
|
|
509
|
+
timestamp,
|
|
510
|
+
event_name,
|
|
511
|
+
func_qual,
|
|
512
|
+
show_tid,
|
|
513
|
+
process_name,
|
|
514
|
+
args,
|
|
515
|
+
):
|
|
516
|
+
"""Print kevents received from the device in real time."""
|
|
477
517
|
|
|
478
518
|
parser = PyKdebugParser()
|
|
479
519
|
parser.filter_class = class_filters
|
|
480
520
|
if bsc:
|
|
481
|
-
subclass_filters = list(subclass_filters)
|
|
521
|
+
subclass_filters = [*list(subclass_filters), BSC_SUBCLASS]
|
|
482
522
|
parser.filter_subclass = subclass_filters
|
|
483
523
|
filters = parse_filters(subclass_filters, class_filters)
|
|
484
524
|
parser.filter_tid = tid
|
|
@@ -491,78 +531,75 @@ def live_profile_session(service_provider: LockdownClient, count, bsc, class_fil
|
|
|
491
531
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
492
532
|
trace_codes_map = DeviceInfo(dvt).trace_codes()
|
|
493
533
|
time_config = CoreProfileSessionTap.get_time_config(dvt)
|
|
494
|
-
parser.numer = time_config[
|
|
495
|
-
parser.denom = time_config[
|
|
496
|
-
parser.mach_absolute_time = time_config[
|
|
497
|
-
parser.usecs_since_epoch = time_config[
|
|
498
|
-
parser.timezone = time_config[
|
|
534
|
+
parser.numer = time_config["numer"]
|
|
535
|
+
parser.denom = time_config["denom"]
|
|
536
|
+
parser.mach_absolute_time = time_config["mach_absolute_time"]
|
|
537
|
+
parser.usecs_since_epoch = time_config["usecs_since_epoch"]
|
|
538
|
+
parser.timezone = time_config["timezone"]
|
|
499
539
|
with CoreProfileSessionTap(dvt, time_config, filters) as tap:
|
|
500
|
-
i
|
|
501
|
-
for event in parser.formatted_kevents(tap.get_kdbuf_stream(), trace_codes_map):
|
|
540
|
+
for i, event in enumerate(parser.formatted_kevents(tap.get_kdbuf_stream(), trace_codes_map)):
|
|
502
541
|
print(event)
|
|
503
|
-
i += 1
|
|
504
542
|
if i == count:
|
|
505
543
|
break
|
|
506
544
|
|
|
507
545
|
|
|
508
|
-
@core_profile_session.command(
|
|
509
|
-
@click.argument(
|
|
546
|
+
@core_profile_session.command("save", cls=Command)
|
|
547
|
+
@click.argument("out", type=click.File("wb"))
|
|
510
548
|
@bsc_filter
|
|
511
549
|
@class_filter
|
|
512
550
|
@subclass_filter
|
|
513
551
|
def save_profile_session(service_provider: LockdownClient, out, bsc, class_filters, subclass_filters):
|
|
514
|
-
"""
|
|
552
|
+
"""Dump core profiling information."""
|
|
515
553
|
if bsc:
|
|
516
|
-
subclass_filters = list(subclass_filters)
|
|
554
|
+
subclass_filters = [*list(subclass_filters), BSC_SUBCLASS]
|
|
517
555
|
filters = parse_filters(subclass_filters, class_filters)
|
|
518
|
-
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
519
|
-
|
|
520
|
-
tap.dump(out)
|
|
556
|
+
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, CoreProfileSessionTap(dvt, {}, filters) as tap:
|
|
557
|
+
tap.dump(out)
|
|
521
558
|
|
|
522
559
|
|
|
523
|
-
@core_profile_session.command(
|
|
524
|
-
@click.option(
|
|
560
|
+
@core_profile_session.command("stackshot", cls=Command)
|
|
561
|
+
@click.option("--out", type=click.File("w"), default=None)
|
|
525
562
|
def stackshot(service_provider: LockdownClient, out):
|
|
526
|
-
"""
|
|
527
|
-
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
print_json(data)
|
|
563
|
+
"""Dump stackshot information."""
|
|
564
|
+
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, CoreProfileSessionTap(dvt, {}) as tap:
|
|
565
|
+
try:
|
|
566
|
+
data = tap.get_stackshot()
|
|
567
|
+
except ExtractingStackshotError:
|
|
568
|
+
logger.exception("Extracting stackshot failed")
|
|
569
|
+
return
|
|
570
|
+
|
|
571
|
+
if out is not None:
|
|
572
|
+
json.dump(data, out, indent=4, default=default_json_encoder)
|
|
573
|
+
else:
|
|
574
|
+
print_json(data)
|
|
539
575
|
|
|
540
576
|
|
|
541
|
-
@core_profile_session.command(
|
|
542
|
-
@click.option(
|
|
543
|
-
@click.option(
|
|
544
|
-
@click.option(
|
|
577
|
+
@core_profile_session.command("parse-live", cls=Command)
|
|
578
|
+
@click.option("-c", "--count", type=click.INT, default=-1, help="Number of events to print. Omit to endless sniff.")
|
|
579
|
+
@click.option("--tid", type=click.INT, default=None, help="Thread ID to filter. Omit for all.")
|
|
580
|
+
@click.option("--show-tid/--no-show-tid", default=False, help="Whether to print thread id or not.")
|
|
545
581
|
@bsc_filter
|
|
546
582
|
@class_filter
|
|
547
583
|
@subclass_filter
|
|
548
|
-
@click.option(
|
|
549
|
-
def parse_live_profile_session(
|
|
550
|
-
|
|
551
|
-
|
|
584
|
+
@click.option("--process", default=None, help="Process ID / name to filter. Omit for all.")
|
|
585
|
+
def parse_live_profile_session(
|
|
586
|
+
service_provider: LockdownClient, count, tid, show_tid, bsc, class_filters, subclass_filters, process
|
|
587
|
+
):
|
|
588
|
+
"""Print traces (syscalls, thread events, etc.) received from the device in real time."""
|
|
552
589
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
553
|
-
print(
|
|
590
|
+
print("Receiving time information")
|
|
554
591
|
time_config = CoreProfileSessionTap.get_time_config(dvt)
|
|
555
592
|
parser = PyKdebugParser()
|
|
556
593
|
parser.filter_class = list(class_filters)
|
|
557
594
|
if bsc:
|
|
558
|
-
subclass_filters = list(subclass_filters)
|
|
595
|
+
subclass_filters = [*list(subclass_filters), BSC_SUBCLASS]
|
|
559
596
|
parser.filter_subclass = subclass_filters
|
|
560
597
|
filters = parse_filters(subclass_filters, class_filters)
|
|
561
|
-
parser.numer = time_config[
|
|
562
|
-
parser.denom = time_config[
|
|
563
|
-
parser.mach_absolute_time = time_config[
|
|
564
|
-
parser.usecs_since_epoch = time_config[
|
|
565
|
-
parser.timezone = time_config[
|
|
598
|
+
parser.numer = time_config["numer"]
|
|
599
|
+
parser.denom = time_config["denom"]
|
|
600
|
+
parser.mach_absolute_time = time_config["mach_absolute_time"]
|
|
601
|
+
parser.usecs_since_epoch = time_config["usecs_since_epoch"]
|
|
602
|
+
parser.timezone = time_config["timezone"]
|
|
566
603
|
parser.filter_tid = tid
|
|
567
604
|
parser.filter_process = process
|
|
568
605
|
parser.show_tid = show_tid
|
|
@@ -570,14 +607,12 @@ def parse_live_profile_session(service_provider: LockdownClient, count, tid, sho
|
|
|
570
607
|
|
|
571
608
|
with CoreProfileSessionTap(dvt, time_config, filters) as tap:
|
|
572
609
|
if show_tid:
|
|
573
|
-
print(
|
|
610
|
+
print("{:^32}|{:^11}|{:^33}| Event".format("Time", "Thread", "Process"))
|
|
574
611
|
else:
|
|
575
|
-
print(
|
|
612
|
+
print("{:^32}|{:^33}| Event".format("Time", "Process"))
|
|
576
613
|
|
|
577
|
-
i
|
|
578
|
-
for trace in parser.formatted_traces(tap.get_kdbuf_stream()):
|
|
614
|
+
for i, trace in enumerate(parser.formatted_traces(tap.get_kdbuf_stream())):
|
|
579
615
|
print(trace, flush=True)
|
|
580
|
-
i += 1
|
|
581
616
|
if i == count:
|
|
582
617
|
break
|
|
583
618
|
|
|
@@ -594,152 +629,150 @@ def get_image_name(dsc_uuid_map, image_uuid, current_dsc_map):
|
|
|
594
629
|
def format_callstack(callstack, dsc_uuid_map, current_dsc_map):
|
|
595
630
|
lines = callstack.splitlines()
|
|
596
631
|
for i, line in enumerate(lines[1:]):
|
|
597
|
-
if
|
|
598
|
-
uuid = line.split(
|
|
632
|
+
if ":" in line:
|
|
633
|
+
uuid = line.split(":")[0].strip()
|
|
599
634
|
lines[i + 1] = line.replace(uuid, get_image_name(dsc_uuid_map, uuid, current_dsc_map))
|
|
600
|
-
return
|
|
635
|
+
return "\n".join(lines)
|
|
601
636
|
|
|
602
637
|
|
|
603
|
-
@core_profile_session.command(
|
|
604
|
-
@click.option(
|
|
605
|
-
@click.option(
|
|
606
|
-
@click.option(
|
|
607
|
-
@click.option(
|
|
638
|
+
@core_profile_session.command("callstacks-live", cls=Command)
|
|
639
|
+
@click.option("-c", "--count", type=click.INT, default=-1, help="Number of events to print. Omit to endless sniff.")
|
|
640
|
+
@click.option("--process", default=None, help="Process to filter. Omit for all.")
|
|
641
|
+
@click.option("--tid", type=click.INT, default=None, help="Thread ID to filter. Omit for all.")
|
|
642
|
+
@click.option("--show-tid/--no-show-tid", default=False, help="Whether to print thread id or not.")
|
|
608
643
|
def callstacks_live_profile_session(service_provider: LockdownClient, count, process, tid, show_tid):
|
|
609
|
-
"""
|
|
644
|
+
"""Print callstacks received from the device in real time."""
|
|
610
645
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
611
|
-
print(
|
|
646
|
+
print("Receiving time information")
|
|
612
647
|
time_config = CoreProfileSessionTap.get_time_config(dvt)
|
|
613
648
|
parser = PyKdebugParser()
|
|
614
|
-
parser.numer = time_config[
|
|
615
|
-
parser.denom = time_config[
|
|
616
|
-
parser.mach_absolute_time = time_config[
|
|
617
|
-
parser.usecs_since_epoch = time_config[
|
|
618
|
-
parser.timezone = time_config[
|
|
649
|
+
parser.numer = time_config["numer"]
|
|
650
|
+
parser.denom = time_config["denom"]
|
|
651
|
+
parser.mach_absolute_time = time_config["mach_absolute_time"]
|
|
652
|
+
parser.usecs_since_epoch = time_config["usecs_since_epoch"]
|
|
653
|
+
parser.timezone = time_config["timezone"]
|
|
619
654
|
parser.filter_tid = tid
|
|
620
655
|
parser.filter_process = process
|
|
621
656
|
parser.color = user_requested_colored_output()
|
|
622
657
|
parser.show_tid = show_tid
|
|
623
658
|
|
|
624
|
-
with open(os.path.join(pymobiledevice3.__path__[0],
|
|
659
|
+
with open(os.path.join(pymobiledevice3.__path__[0], "resources", "dsc_uuid_map.json")) as fd:
|
|
625
660
|
dsc_uuid_map = json.load(fd)
|
|
626
661
|
|
|
627
662
|
current_dsc_map = {}
|
|
628
663
|
with CoreProfileSessionTap(dvt, time_config) as tap:
|
|
629
|
-
i
|
|
630
|
-
for callstack in parser.formatted_callstacks(tap.get_kdbuf_stream()):
|
|
664
|
+
for i, callstack in enumerate(parser.formatted_callstacks(tap.get_kdbuf_stream())):
|
|
631
665
|
print(format_callstack(callstack, dsc_uuid_map, current_dsc_map))
|
|
632
|
-
i += 1
|
|
633
666
|
if i == count:
|
|
634
667
|
break
|
|
635
668
|
|
|
636
669
|
|
|
637
|
-
@dvt.command(
|
|
670
|
+
@dvt.command("trace-codes", cls=Command)
|
|
638
671
|
def dvt_trace_codes(service_provider: LockdownClient):
|
|
639
|
-
"""
|
|
672
|
+
"""Print KDebug trace codes."""
|
|
640
673
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
641
674
|
device_info = DeviceInfo(dvt)
|
|
642
675
|
print_json({hex(k): v for k, v in device_info.trace_codes().items()})
|
|
643
676
|
|
|
644
677
|
|
|
645
|
-
@dvt.command(
|
|
646
|
-
@click.argument(
|
|
678
|
+
@dvt.command("name-for-uid", cls=Command)
|
|
679
|
+
@click.argument("uid", type=click.INT)
|
|
647
680
|
def dvt_name_for_uid(service_provider: LockdownClient, uid):
|
|
648
|
-
"""
|
|
681
|
+
"""Print the assiciated username for the given uid."""
|
|
649
682
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
650
683
|
device_info = DeviceInfo(dvt)
|
|
651
684
|
print(device_info.name_for_uid(uid))
|
|
652
685
|
|
|
653
686
|
|
|
654
|
-
@dvt.command(
|
|
655
|
-
@click.argument(
|
|
687
|
+
@dvt.command("name-for-gid", cls=Command)
|
|
688
|
+
@click.argument("gid", type=click.INT)
|
|
656
689
|
def dvt_name_for_gid(service_provider: LockdownClient, gid):
|
|
657
|
-
"""
|
|
690
|
+
"""Print the assiciated group name for the given gid."""
|
|
658
691
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
659
692
|
device_info = DeviceInfo(dvt)
|
|
660
693
|
print(device_info.name_for_gid(gid))
|
|
661
694
|
|
|
662
695
|
|
|
663
|
-
@dvt.command(
|
|
664
|
-
@click.option(
|
|
696
|
+
@dvt.command("oslog", cls=Command)
|
|
697
|
+
@click.option("--pid", type=click.INT)
|
|
665
698
|
def dvt_oslog(service_provider: LockdownClient, pid):
|
|
666
|
-
"""
|
|
667
|
-
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
699
|
+
"""Sniff device oslog (not very stable, but includes more data and normal syslog)"""
|
|
700
|
+
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, ActivityTraceTap(dvt) as tap:
|
|
701
|
+
for message in tap:
|
|
702
|
+
message_pid = message.process
|
|
703
|
+
# without message_type maybe signpost have event_type
|
|
704
|
+
message_type = (
|
|
705
|
+
message.message_type
|
|
706
|
+
if hasattr(message, "message_type")
|
|
707
|
+
else message.event_type
|
|
708
|
+
if hasattr(message, "event_type")
|
|
709
|
+
else "unknown"
|
|
710
|
+
)
|
|
711
|
+
sender_image_path = message.sender_image_path
|
|
712
|
+
image_name = os.path.basename(sender_image_path)
|
|
713
|
+
subsystem = message.subsystem
|
|
714
|
+
category = message.category
|
|
715
|
+
timestamp = datetime.now()
|
|
716
|
+
|
|
717
|
+
if pid is not None and message_pid != pid:
|
|
718
|
+
continue
|
|
719
|
+
|
|
720
|
+
formatted_message = decode_message_format(message.message) if message.message else message.name
|
|
721
|
+
|
|
722
|
+
if user_requested_colored_output():
|
|
723
|
+
timestamp = click.style(str(timestamp), bold=True)
|
|
724
|
+
message_pid = click.style(str(message_pid), "magenta")
|
|
725
|
+
subsystem = click.style(subsystem, "green")
|
|
726
|
+
category = click.style(category, "green")
|
|
727
|
+
image_name = click.style(image_name, "yellow")
|
|
728
|
+
message_type = click.style(message_type, "cyan")
|
|
729
|
+
|
|
730
|
+
print(
|
|
731
|
+
f"[{timestamp}][{subsystem}][{category}][{message_pid}][{image_name}] "
|
|
732
|
+
f"<{message_type}>: {formatted_message}"
|
|
733
|
+
)
|
|
698
734
|
|
|
699
735
|
|
|
700
|
-
@dvt.command(
|
|
701
|
-
@click.argument(
|
|
736
|
+
@dvt.command("energy", cls=Command)
|
|
737
|
+
@click.argument("pid-list", nargs=-1)
|
|
702
738
|
def dvt_energy(service_provider: LockdownClient, pid_list):
|
|
703
|
-
"""
|
|
739
|
+
"""Monitor the energy consumption for given PIDs"""
|
|
704
740
|
|
|
705
741
|
if len(pid_list) == 0:
|
|
706
|
-
logger.error(
|
|
742
|
+
logger.error("pid_list must not be empty")
|
|
707
743
|
return
|
|
708
744
|
|
|
709
745
|
pid_list = [int(pid) for pid in pid_list]
|
|
710
746
|
|
|
711
|
-
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
logger.info(telemetry)
|
|
747
|
+
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, EnergyMonitor(dvt, pid_list) as energy_monitor:
|
|
748
|
+
for telemetry in energy_monitor:
|
|
749
|
+
logger.info(telemetry)
|
|
715
750
|
|
|
716
751
|
|
|
717
|
-
@dvt.command(
|
|
752
|
+
@dvt.command("notifications", cls=Command)
|
|
718
753
|
def dvt_notifications(service_provider: LockdownClient):
|
|
719
|
-
"""
|
|
720
|
-
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
logger.info(notification)
|
|
754
|
+
"""Monitor memory and app notifications"""
|
|
755
|
+
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, Notifications(dvt) as notifications:
|
|
756
|
+
for notification in notifications:
|
|
757
|
+
logger.info(notification)
|
|
724
758
|
|
|
725
759
|
|
|
726
|
-
@dvt.command(
|
|
727
|
-
def
|
|
728
|
-
"""
|
|
729
|
-
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
logger.info(stats)
|
|
760
|
+
@dvt.command("graphics", cls=Command)
|
|
761
|
+
def dvt_graphics(service_provider: LockdownClient):
|
|
762
|
+
"""Monitor graphics-related information"""
|
|
763
|
+
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, Graphics(dvt) as graphics:
|
|
764
|
+
for stats in graphics:
|
|
765
|
+
logger.info(stats)
|
|
733
766
|
|
|
734
767
|
|
|
735
|
-
@developer.group(
|
|
768
|
+
@developer.group("fetch-symbols")
|
|
736
769
|
def fetch_symbols():
|
|
737
|
-
"""
|
|
770
|
+
"""Download the DSC (and dyld) from the device"""
|
|
738
771
|
pass
|
|
739
772
|
|
|
740
773
|
|
|
741
774
|
async def fetch_symbols_list_task(service_provider: LockdownServiceProvider) -> None:
|
|
742
|
-
if Version(service_provider.product_version) < Version(
|
|
775
|
+
if Version(service_provider.product_version) < Version("17.0"):
|
|
743
776
|
print_json(DtFetchSymbols(service_provider).list_files())
|
|
744
777
|
else:
|
|
745
778
|
if not isinstance(service_provider, RemoteServiceDiscoveryService):
|
|
@@ -749,9 +782,9 @@ async def fetch_symbols_list_task(service_provider: LockdownServiceProvider) ->
|
|
|
749
782
|
print_json([f.file_path for f in await fetch_symbols.get_dsc_file_list()])
|
|
750
783
|
|
|
751
784
|
|
|
752
|
-
@fetch_symbols.command(
|
|
785
|
+
@fetch_symbols.command("list", cls=Command)
|
|
753
786
|
def fetch_symbols_list(service_provider: LockdownServiceProvider) -> None:
|
|
754
|
-
"""
|
|
787
|
+
"""list of files to be downloaded"""
|
|
755
788
|
asyncio.run(fetch_symbols_list_task(service_provider), debug=True)
|
|
756
789
|
|
|
757
790
|
|
|
@@ -759,14 +792,14 @@ async def fetch_symbols_download_task(service_provider: LockdownServiceProvider,
|
|
|
759
792
|
out = Path(out)
|
|
760
793
|
out.mkdir(parents=True, exist_ok=True)
|
|
761
794
|
|
|
762
|
-
if Version(service_provider.product_version) < Version(
|
|
795
|
+
if Version(service_provider.product_version) < Version("17.0"):
|
|
763
796
|
fetch_symbols = DtFetchSymbols(service_provider)
|
|
764
797
|
files = fetch_symbols.list_files()
|
|
765
798
|
|
|
766
799
|
downloaded_files = set()
|
|
767
800
|
|
|
768
801
|
for i, file in enumerate(files):
|
|
769
|
-
if file.startswith(
|
|
802
|
+
if file.startswith("/"):
|
|
770
803
|
# trim root to allow relative download
|
|
771
804
|
file = file[1:]
|
|
772
805
|
file = out / file
|
|
@@ -777,9 +810,9 @@ async def fetch_symbols_download_task(service_provider: LockdownServiceProvider,
|
|
|
777
810
|
|
|
778
811
|
downloaded_files.add(file)
|
|
779
812
|
file.parent.mkdir(parents=True, exist_ok=True)
|
|
780
|
-
with open(file,
|
|
813
|
+
with open(file, "ab") as f:
|
|
781
814
|
# same file may appear twice, so we'll need to append data into it
|
|
782
|
-
logger.info(f
|
|
815
|
+
logger.info(f"writing to: {file}")
|
|
783
816
|
fetch_symbols.get_file(i, f)
|
|
784
817
|
else:
|
|
785
818
|
if not isinstance(service_provider, RemoteServiceDiscoveryService):
|
|
@@ -788,28 +821,28 @@ async def fetch_symbols_download_task(service_provider: LockdownServiceProvider,
|
|
|
788
821
|
await fetch_symbols.download(out)
|
|
789
822
|
|
|
790
823
|
|
|
791
|
-
@fetch_symbols.command(
|
|
792
|
-
@click.argument(
|
|
824
|
+
@fetch_symbols.command("download", cls=Command)
|
|
825
|
+
@click.argument("out", type=click.Path(dir_okay=True, file_okay=False))
|
|
793
826
|
def fetch_symbols_download(service_provider: LockdownServiceProvider, out: str) -> None:
|
|
794
|
-
"""
|
|
827
|
+
"""download the linker and dyld cache to a specified directory"""
|
|
795
828
|
asyncio.run(fetch_symbols_download_task(service_provider, out), debug=True)
|
|
796
829
|
|
|
797
830
|
|
|
798
|
-
@developer.group(
|
|
831
|
+
@developer.group("simulate-location")
|
|
799
832
|
def simulate_location():
|
|
800
|
-
"""
|
|
833
|
+
"""Simulate device location by given input"""
|
|
801
834
|
pass
|
|
802
835
|
|
|
803
836
|
|
|
804
|
-
@simulate_location.command(
|
|
837
|
+
@simulate_location.command("clear", cls=Command)
|
|
805
838
|
def simulate_location_clear(service_provider: LockdownClient):
|
|
806
|
-
"""
|
|
839
|
+
"""clear simulated location"""
|
|
807
840
|
DtSimulateLocation(service_provider).clear()
|
|
808
841
|
|
|
809
842
|
|
|
810
|
-
@simulate_location.command(
|
|
811
|
-
@click.argument(
|
|
812
|
-
@click.argument(
|
|
843
|
+
@simulate_location.command("set", cls=Command)
|
|
844
|
+
@click.argument("latitude", type=click.FLOAT)
|
|
845
|
+
@click.argument("longitude", type=click.FLOAT)
|
|
813
846
|
def simulate_location_set(service_provider: LockdownClient, latitude, longitude):
|
|
814
847
|
"""
|
|
815
848
|
set a simulated location.
|
|
@@ -819,58 +852,58 @@ def simulate_location_set(service_provider: LockdownClient, latitude, longitude)
|
|
|
819
852
|
DtSimulateLocation(service_provider).set(latitude, longitude)
|
|
820
853
|
|
|
821
854
|
|
|
822
|
-
@simulate_location.command(
|
|
823
|
-
@click.argument(
|
|
824
|
-
@click.argument(
|
|
825
|
-
@click.option(
|
|
855
|
+
@simulate_location.command("play", cls=Command)
|
|
856
|
+
@click.argument("filename", type=click.Path(exists=True, file_okay=True, dir_okay=False))
|
|
857
|
+
@click.argument("timing_randomness_range", type=click.INT)
|
|
858
|
+
@click.option("--disable-sleep", is_flag=True, default=False)
|
|
826
859
|
def simulate_location_play(service_provider: LockdownClient, filename, timing_randomness_range, disable_sleep):
|
|
827
|
-
"""
|
|
860
|
+
"""play a .gpx file"""
|
|
828
861
|
DtSimulateLocation(service_provider).play_gpx_file(filename, timing_randomness_range, disable_sleep=disable_sleep)
|
|
829
862
|
|
|
830
863
|
|
|
831
|
-
@developer.group(
|
|
864
|
+
@developer.group("accessibility")
|
|
832
865
|
def accessibility():
|
|
833
|
-
"""
|
|
866
|
+
"""Interact with accessibility-related features"""
|
|
834
867
|
pass
|
|
835
868
|
|
|
836
869
|
|
|
837
|
-
@accessibility.command(
|
|
838
|
-
@click.argument(
|
|
870
|
+
@accessibility.command("run-audit", cls=Command)
|
|
871
|
+
@click.argument("test_types", nargs=-1)
|
|
839
872
|
def accessibility_run_audit(service_provider: LockdownServiceProvider, test_types):
|
|
840
|
-
"""
|
|
873
|
+
"""runs accessibility audit tests"""
|
|
841
874
|
param = list(test_types)
|
|
842
875
|
audit_issues = AccessibilityAudit(service_provider).run_audit(param)
|
|
843
876
|
print_json([audit_issue.json() for audit_issue in audit_issues], False)
|
|
844
877
|
|
|
845
878
|
|
|
846
|
-
@accessibility.command(
|
|
879
|
+
@accessibility.command("supported-audit-types", cls=Command)
|
|
847
880
|
def accessibility_supported_audit_types(service_provider: LockdownServiceProvider):
|
|
848
|
-
"""
|
|
881
|
+
"""lists supported accessibility audit test types"""
|
|
849
882
|
print_json(AccessibilityAudit(service_provider).supported_audits_types())
|
|
850
883
|
|
|
851
884
|
|
|
852
|
-
@accessibility.command(
|
|
885
|
+
@accessibility.command("capabilities", cls=Command)
|
|
853
886
|
def accessibility_capabilities(service_provider: LockdownClient):
|
|
854
|
-
"""
|
|
887
|
+
"""display accessibility capabilities"""
|
|
855
888
|
print_json(AccessibilityAudit(service_provider).capabilities)
|
|
856
889
|
|
|
857
890
|
|
|
858
|
-
@accessibility.group(
|
|
891
|
+
@accessibility.group("settings")
|
|
859
892
|
def accessibility_settings():
|
|
860
|
-
"""
|
|
893
|
+
"""accessibility settings."""
|
|
861
894
|
pass
|
|
862
895
|
|
|
863
896
|
|
|
864
|
-
@accessibility_settings.command(
|
|
897
|
+
@accessibility_settings.command("show", cls=Command)
|
|
865
898
|
def accessibility_settings_show(service_provider: LockdownClient):
|
|
866
|
-
"""
|
|
899
|
+
"""show current settings"""
|
|
867
900
|
for setting in AccessibilityAudit(service_provider).settings:
|
|
868
901
|
print(setting)
|
|
869
902
|
|
|
870
903
|
|
|
871
|
-
@accessibility_settings.command(
|
|
872
|
-
@click.argument(
|
|
873
|
-
@click.argument(
|
|
904
|
+
@accessibility_settings.command("set", cls=Command)
|
|
905
|
+
@click.argument("setting")
|
|
906
|
+
@click.argument("value")
|
|
874
907
|
def accessibility_settings_set(service_provider: LockdownClient, setting, value):
|
|
875
908
|
"""
|
|
876
909
|
change current settings
|
|
@@ -882,7 +915,7 @@ def accessibility_settings_set(service_provider: LockdownClient, setting, value)
|
|
|
882
915
|
OSUTILS.wait_return()
|
|
883
916
|
|
|
884
917
|
|
|
885
|
-
@accessibility_settings.command(
|
|
918
|
+
@accessibility_settings.command("reset", cls=Command)
|
|
886
919
|
def accessibility_settings_reset(service_provider: LockdownClient):
|
|
887
920
|
"""
|
|
888
921
|
reset accessibility settings to default
|
|
@@ -891,25 +924,27 @@ def accessibility_settings_reset(service_provider: LockdownClient):
|
|
|
891
924
|
service.reset_settings()
|
|
892
925
|
|
|
893
926
|
|
|
894
|
-
@accessibility.command(
|
|
927
|
+
@accessibility.command("shell", cls=Command)
|
|
895
928
|
def accessibility_shell(service_provider: LockdownClient):
|
|
896
|
-
"""
|
|
929
|
+
"""start and ipython accessibility shell"""
|
|
897
930
|
AccessibilityAudit(service_provider).shell()
|
|
898
931
|
|
|
899
932
|
|
|
900
|
-
@accessibility.command(
|
|
933
|
+
@accessibility.command("notifications", cls=Command)
|
|
901
934
|
def accessibility_notifications(service_provider: LockdownClient):
|
|
902
|
-
"""
|
|
935
|
+
"""show notifications"""
|
|
903
936
|
|
|
904
937
|
service = AccessibilityAudit(service_provider)
|
|
905
938
|
for event in service.iter_events():
|
|
906
|
-
if event.name in (
|
|
907
|
-
|
|
939
|
+
if event.name in (
|
|
940
|
+
"hostAppStateChanged:",
|
|
941
|
+
"hostInspectorCurrentElementChanged:",
|
|
942
|
+
):
|
|
908
943
|
for focus_item in event.data:
|
|
909
944
|
logger.info(focus_item)
|
|
910
945
|
|
|
911
946
|
|
|
912
|
-
@accessibility.command(
|
|
947
|
+
@accessibility.command("list-items", cls=Command)
|
|
913
948
|
def accessibility_list_items(service_provider: LockdownClient):
|
|
914
949
|
"""List elements available in the currently shown menu."""
|
|
915
950
|
|
|
@@ -920,56 +955,56 @@ def accessibility_list_items(service_provider: LockdownClient):
|
|
|
920
955
|
print_json(elements)
|
|
921
956
|
|
|
922
957
|
|
|
923
|
-
@developer.group(
|
|
958
|
+
@developer.group("condition")
|
|
924
959
|
def condition():
|
|
925
|
-
"""
|
|
960
|
+
"""Force a predefined condition"""
|
|
926
961
|
pass
|
|
927
962
|
|
|
928
963
|
|
|
929
|
-
@condition.command(
|
|
964
|
+
@condition.command("list", cls=Command)
|
|
930
965
|
def condition_list(service_provider: LockdownServiceProvider) -> None:
|
|
931
|
-
"""
|
|
966
|
+
"""list all available conditions"""
|
|
932
967
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
933
968
|
print_json(ConditionInducer(dvt).list())
|
|
934
969
|
|
|
935
970
|
|
|
936
|
-
@condition.command(
|
|
971
|
+
@condition.command("clear", cls=Command)
|
|
937
972
|
def condition_clear(service_provider: LockdownClient):
|
|
938
|
-
"""
|
|
973
|
+
"""clear current condition"""
|
|
939
974
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
940
975
|
ConditionInducer(dvt).clear()
|
|
941
976
|
|
|
942
977
|
|
|
943
|
-
@condition.command(
|
|
944
|
-
@click.argument(
|
|
978
|
+
@condition.command("set", cls=Command)
|
|
979
|
+
@click.argument("profile_identifier")
|
|
945
980
|
def condition_set(service_provider: LockdownClient, profile_identifier):
|
|
946
|
-
"""
|
|
981
|
+
"""set a specific condition"""
|
|
947
982
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
948
983
|
ConditionInducer(dvt).set(profile_identifier)
|
|
949
984
|
OSUTILS.wait_return()
|
|
950
985
|
|
|
951
986
|
|
|
952
987
|
@developer.command(cls=Command)
|
|
953
|
-
@click.argument(
|
|
988
|
+
@click.argument("out", type=click.File("wb"))
|
|
954
989
|
def screenshot(service_provider: LockdownClient, out):
|
|
955
|
-
"""
|
|
990
|
+
"""Take a screenshot in PNG format"""
|
|
956
991
|
out.write(ScreenshotService(lockdown=service_provider).take_screenshot())
|
|
957
992
|
|
|
958
993
|
|
|
959
|
-
@developer.group(
|
|
994
|
+
@developer.group("debugserver")
|
|
960
995
|
def debugserver():
|
|
961
|
-
"""
|
|
996
|
+
"""Interact with debugserver"""
|
|
962
997
|
pass
|
|
963
998
|
|
|
964
999
|
|
|
965
|
-
@debugserver.command(
|
|
1000
|
+
@debugserver.command("applist", cls=Command)
|
|
966
1001
|
def debugserver_applist(service_provider: LockdownClient):
|
|
967
|
-
"""
|
|
1002
|
+
"""Get applist xml"""
|
|
968
1003
|
print_json(DebugServerAppList(service_provider).get())
|
|
969
1004
|
|
|
970
1005
|
|
|
971
|
-
@debugserver.command(
|
|
972
|
-
@click.argument(
|
|
1006
|
+
@debugserver.command("start-server", cls=Command)
|
|
1007
|
+
@click.argument("local_port", type=click.INT, required=False)
|
|
973
1008
|
def debugserver_start_server(service_provider: LockdownClient, local_port: Optional[int] = None):
|
|
974
1009
|
"""
|
|
975
1010
|
if local_port is provided, start a debugserver at remote listening on a given port locally.
|
|
@@ -979,17 +1014,17 @@ def debugserver_start_server(service_provider: LockdownClient, local_port: Optio
|
|
|
979
1014
|
This can be done using the following commands within lldb shell.
|
|
980
1015
|
"""
|
|
981
1016
|
|
|
982
|
-
if Version(service_provider.product_version) < Version(
|
|
983
|
-
service_name =
|
|
1017
|
+
if Version(service_provider.product_version) < Version("17.0"):
|
|
1018
|
+
service_name = "com.apple.debugserver.DVTSecureSocketProxy"
|
|
984
1019
|
else:
|
|
985
|
-
service_name =
|
|
1020
|
+
service_name = "com.apple.internal.dt.remote.debugproxy"
|
|
986
1021
|
|
|
987
1022
|
if local_port is not None:
|
|
988
|
-
print(DEBUGSERVER_CONNECTION_STEPS.format(host=
|
|
989
|
-
print(
|
|
1023
|
+
print(DEBUGSERVER_CONNECTION_STEPS.format(host="127.0.0.1", port=local_port))
|
|
1024
|
+
print("Started port forwarding. Press Ctrl-C to close this shell when done")
|
|
990
1025
|
sys.stdout.flush()
|
|
991
1026
|
LockdownTcpForwarder(service_provider, local_port, service_name).start()
|
|
992
|
-
elif Version(service_provider.product_version) >= Version(
|
|
1027
|
+
elif Version(service_provider.product_version) >= Version("17.0"):
|
|
993
1028
|
if not isinstance(service_provider, RemoteServiceDiscoveryService):
|
|
994
1029
|
raise RSDRequiredError(service_provider.identifier)
|
|
995
1030
|
debugserver_port = service_provider.get_service_port(service_name)
|
|
@@ -998,21 +1033,21 @@ def debugserver_start_server(service_provider: LockdownClient, local_port: Optio
|
|
|
998
1033
|
print("local_port is required for iOS < 17.0")
|
|
999
1034
|
|
|
1000
1035
|
|
|
1001
|
-
@debugserver.command(
|
|
1002
|
-
@click.argument(
|
|
1003
|
-
@click.option(
|
|
1004
|
-
@click.option(
|
|
1005
|
-
@click.option(
|
|
1006
|
-
@click.option(
|
|
1007
|
-
@click.option(
|
|
1036
|
+
@debugserver.command("lldb", cls=RSDCommand)
|
|
1037
|
+
@click.argument("xcodeproj_path", type=click.Path(exists=True, file_okay=False, dir_okay=True))
|
|
1038
|
+
@click.option("--configuration", default="Debug", help="Usually Release/Debug")
|
|
1039
|
+
@click.option("--lldb-command", default="lldb")
|
|
1040
|
+
@click.option("--launch", is_flag=True, default=False, help="Launch the app after connecting to lldb")
|
|
1041
|
+
@click.option("breakpoints", "-b", "--break", multiple=True, help="Add multiple startup breakpoints")
|
|
1042
|
+
@click.option("user_commands", "--command", "-c", multiple=True, help="Additional commands to run at startup")
|
|
1008
1043
|
def debugserver_lldb(
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1044
|
+
service_provider: LockdownServiceProvider,
|
|
1045
|
+
xcodeproj_path: str,
|
|
1046
|
+
configuration: str,
|
|
1047
|
+
lldb_command: str,
|
|
1048
|
+
launch: bool,
|
|
1049
|
+
breakpoints: tuple[str],
|
|
1050
|
+
user_commands: tuple[str],
|
|
1016
1051
|
) -> None:
|
|
1017
1052
|
"""
|
|
1018
1053
|
Automate lldb launch for a given xcodeproj.
|
|
@@ -1027,49 +1062,49 @@ def debugserver_lldb(
|
|
|
1027
1062
|
- Execute any additional commands if requested
|
|
1028
1063
|
- Switch to lldb shell
|
|
1029
1064
|
"""
|
|
1030
|
-
if Version(service_provider.product_version) < Version(
|
|
1031
|
-
logger.error(
|
|
1065
|
+
if Version(service_provider.product_version) < Version("17.0"):
|
|
1066
|
+
logger.error("lldb is only supported on iOS >= 17.0")
|
|
1032
1067
|
return
|
|
1033
1068
|
|
|
1034
1069
|
commands = []
|
|
1035
1070
|
xcodeproj_path = Path(xcodeproj_path)
|
|
1036
1071
|
with local.cwd(xcodeproj_path.parent):
|
|
1037
|
-
logger.info(f
|
|
1038
|
-
local[
|
|
1039
|
-
local_app =
|
|
1040
|
-
logger.info(f
|
|
1072
|
+
logger.info(f"Building {xcodeproj_path} for {configuration} configuration")
|
|
1073
|
+
local["xcodebuild"]["-configuration", configuration, "build"]()
|
|
1074
|
+
local_app = next(iter(Path(f"build/{configuration}-iphoneos").glob("*.app")))
|
|
1075
|
+
logger.info(f"Using app: {local_app}")
|
|
1041
1076
|
|
|
1042
|
-
info_plist_path = local_app /
|
|
1077
|
+
info_plist_path = local_app / "Info.plist"
|
|
1043
1078
|
info_plist = plistlib.loads(info_plist_path.read_bytes())
|
|
1044
|
-
bundle_identifier = info_plist[
|
|
1045
|
-
logger.info(f
|
|
1079
|
+
bundle_identifier = info_plist["CFBundleIdentifier"]
|
|
1080
|
+
logger.info(f"Bundle identifier: {bundle_identifier}")
|
|
1046
1081
|
|
|
1047
|
-
commands.append(
|
|
1082
|
+
commands.append("platform select remote-ios")
|
|
1048
1083
|
commands.append(f'target create "{local_app.absolute()}"')
|
|
1049
1084
|
|
|
1050
1085
|
with InstallationProxyService(create_using_usbmux()) as installation_proxy:
|
|
1051
|
-
logger.info(
|
|
1086
|
+
logger.info("Installing app")
|
|
1052
1087
|
installation_proxy.install_from_local(local_app)
|
|
1053
|
-
remote_path = installation_proxy.get_apps(bundle_identifiers=[bundle_identifier])[bundle_identifier][
|
|
1054
|
-
logger.info(f
|
|
1088
|
+
remote_path = installation_proxy.get_apps(bundle_identifiers=[bundle_identifier])[bundle_identifier]["Path"]
|
|
1089
|
+
logger.info(f"Remote path: {remote_path}")
|
|
1055
1090
|
|
|
1056
1091
|
commands.append(f'script lldb.target.module[0].SetPlatformFileSpec(lldb.SBFileSpec("{remote_path}"))')
|
|
1057
1092
|
|
|
1058
|
-
debugserver_port = service_provider.get_service_port(
|
|
1093
|
+
debugserver_port = service_provider.get_service_port("com.apple.internal.dt.remote.debugproxy")
|
|
1059
1094
|
|
|
1060
1095
|
# Add connection and launch commands
|
|
1061
|
-
commands.append(f
|
|
1096
|
+
commands.append(f"process connect connect://[{service_provider.service.address[0]}]:{debugserver_port}")
|
|
1062
1097
|
|
|
1063
|
-
for
|
|
1064
|
-
commands.append(f'breakpoint set -n "{
|
|
1098
|
+
for bp in breakpoints:
|
|
1099
|
+
commands.append(f'breakpoint set -n "{bp}"')
|
|
1065
1100
|
|
|
1066
1101
|
if launch:
|
|
1067
|
-
commands.append(
|
|
1102
|
+
commands.append("process launch")
|
|
1068
1103
|
|
|
1069
1104
|
# Add user commands
|
|
1070
1105
|
commands += user_commands
|
|
1071
1106
|
|
|
1072
|
-
logger.info(
|
|
1107
|
+
logger.info("Starting lldb with automated setup and connection")
|
|
1073
1108
|
|
|
1074
1109
|
# Works only on unix-based systems, so keep these imports here
|
|
1075
1110
|
import fcntl
|
|
@@ -1085,9 +1120,10 @@ def debugserver_lldb(
|
|
|
1085
1120
|
# Copy terminal size from the current terminal to PTY
|
|
1086
1121
|
def resize_pty() -> None:
|
|
1087
1122
|
"""Update PTY size to match current terminal size"""
|
|
1088
|
-
size = struct.unpack(
|
|
1089
|
-
|
|
1090
|
-
|
|
1123
|
+
size = struct.unpack(
|
|
1124
|
+
"HHHH", fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0))
|
|
1125
|
+
)
|
|
1126
|
+
fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack("HHHH", *size))
|
|
1091
1127
|
# Send SIGWINCH to the child process to notify it of the resize
|
|
1092
1128
|
if process is not None and process.poll() is None:
|
|
1093
1129
|
process.send_signal(signal.SIGWINCH)
|
|
@@ -1107,22 +1143,16 @@ def debugserver_lldb(
|
|
|
1107
1143
|
try:
|
|
1108
1144
|
# Set TERM environment variable to enable colors
|
|
1109
1145
|
env = os.environ.copy()
|
|
1110
|
-
env[
|
|
1111
|
-
|
|
1112
|
-
process = subprocess.Popen(
|
|
1113
|
-
[lldb_command],
|
|
1114
|
-
stdin=slave,
|
|
1115
|
-
stdout=slave,
|
|
1116
|
-
stderr=slave,
|
|
1117
|
-
env=env
|
|
1118
|
-
)
|
|
1146
|
+
env["TERM"] = os.environ.get("TERM", "xterm-256color")
|
|
1147
|
+
|
|
1148
|
+
process = subprocess.Popen([lldb_command], stdin=slave, stdout=slave, stderr=slave, env=env)
|
|
1119
1149
|
os.close(slave)
|
|
1120
1150
|
|
|
1121
1151
|
# Put terminal in raw mode for proper interaction
|
|
1122
1152
|
tty.setraw(sys.stdin.fileno())
|
|
1123
1153
|
# Send all commands through stdin
|
|
1124
1154
|
for command in commands:
|
|
1125
|
-
os.write(master, (command +
|
|
1155
|
+
os.write(master, (command + "\n").encode())
|
|
1126
1156
|
|
|
1127
1157
|
# Now redirect stdin from the terminal to lldb so user can interact
|
|
1128
1158
|
while True:
|
|
@@ -1157,24 +1187,24 @@ def debugserver_lldb(
|
|
|
1157
1187
|
process.wait()
|
|
1158
1188
|
|
|
1159
1189
|
|
|
1160
|
-
@developer.group(
|
|
1190
|
+
@developer.group("arbitration")
|
|
1161
1191
|
def arbitration():
|
|
1162
|
-
"""
|
|
1192
|
+
"""Mark/Unmark device as "in-use" """
|
|
1163
1193
|
pass
|
|
1164
1194
|
|
|
1165
1195
|
|
|
1166
|
-
@arbitration.command(
|
|
1196
|
+
@arbitration.command("version", cls=Command)
|
|
1167
1197
|
def version(service_provider: LockdownClient):
|
|
1168
|
-
"""
|
|
1198
|
+
"""get arbitration version"""
|
|
1169
1199
|
with DtDeviceArbitration(service_provider) as device_arbitration:
|
|
1170
1200
|
print_json(device_arbitration.version)
|
|
1171
1201
|
|
|
1172
1202
|
|
|
1173
|
-
@arbitration.command(
|
|
1174
|
-
@click.argument(
|
|
1175
|
-
@click.option(
|
|
1203
|
+
@arbitration.command("check-in", cls=Command)
|
|
1204
|
+
@click.argument("hostname")
|
|
1205
|
+
@click.option("-f", "--force", default=False, is_flag=True)
|
|
1176
1206
|
def check_in(service_provider: LockdownClient, hostname, force):
|
|
1177
|
-
"""
|
|
1207
|
+
"""owner check-in"""
|
|
1178
1208
|
with DtDeviceArbitration(service_provider) as device_arbitration:
|
|
1179
1209
|
try:
|
|
1180
1210
|
device_arbitration.check_in(hostname, force=force)
|
|
@@ -1183,14 +1213,14 @@ def check_in(service_provider: LockdownClient, hostname, force):
|
|
|
1183
1213
|
logger.error(e.message)
|
|
1184
1214
|
|
|
1185
1215
|
|
|
1186
|
-
@arbitration.command(
|
|
1216
|
+
@arbitration.command("check-out", cls=Command)
|
|
1187
1217
|
def check_out(service_provider: LockdownClient):
|
|
1188
|
-
"""
|
|
1218
|
+
"""owner check-out"""
|
|
1189
1219
|
with DtDeviceArbitration(service_provider) as device_arbitration:
|
|
1190
1220
|
device_arbitration.check_out()
|
|
1191
1221
|
|
|
1192
1222
|
|
|
1193
|
-
@dvt.command(
|
|
1223
|
+
@dvt.command("har", cls=Command)
|
|
1194
1224
|
def dvt_har(service_provider: LockdownClient):
|
|
1195
1225
|
"""
|
|
1196
1226
|
Enable har-logging
|
|
@@ -1199,28 +1229,28 @@ def dvt_har(service_provider: LockdownClient):
|
|
|
1199
1229
|
https://github.com/doronz88/harlogger?tab=readme-ov-file#enable-http-instrumentation-method
|
|
1200
1230
|
"""
|
|
1201
1231
|
with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
|
|
1202
|
-
print(
|
|
1232
|
+
print("> Press Ctrl-C to abort")
|
|
1203
1233
|
with ActivityTraceTap(dvt, enable_http_archive_logging=True) as tap:
|
|
1204
1234
|
while True:
|
|
1205
1235
|
tap.channel.receive_message()
|
|
1206
1236
|
|
|
1207
1237
|
|
|
1208
|
-
@dvt.group(
|
|
1238
|
+
@dvt.group("simulate-location")
|
|
1209
1239
|
def dvt_simulate_location():
|
|
1210
|
-
"""
|
|
1240
|
+
"""Simulate device location by given input"""
|
|
1211
1241
|
pass
|
|
1212
1242
|
|
|
1213
1243
|
|
|
1214
|
-
@dvt_simulate_location.command(
|
|
1244
|
+
@dvt_simulate_location.command("clear", cls=Command)
|
|
1215
1245
|
def dvt_simulate_location_clear(service_provider: LockdownClient):
|
|
1216
|
-
"""
|
|
1246
|
+
"""Clear currently simulated location"""
|
|
1217
1247
|
with DvtSecureSocketProxyService(service_provider) as dvt:
|
|
1218
1248
|
LocationSimulation(dvt).clear()
|
|
1219
1249
|
|
|
1220
1250
|
|
|
1221
|
-
@dvt_simulate_location.command(
|
|
1222
|
-
@click.argument(
|
|
1223
|
-
@click.argument(
|
|
1251
|
+
@dvt_simulate_location.command("set", cls=Command)
|
|
1252
|
+
@click.argument("latitude", type=click.FLOAT)
|
|
1253
|
+
@click.argument("longitude", type=click.FLOAT)
|
|
1224
1254
|
def dvt_simulate_location_set(service_provider: LockdownClient, latitude, longitude):
|
|
1225
1255
|
"""
|
|
1226
1256
|
Set a simulated location.
|
|
@@ -1232,44 +1262,48 @@ def dvt_simulate_location_set(service_provider: LockdownClient, latitude, longit
|
|
|
1232
1262
|
OSUTILS.wait_return()
|
|
1233
1263
|
|
|
1234
1264
|
|
|
1235
|
-
@dvt_simulate_location.command(
|
|
1236
|
-
@click.argument(
|
|
1237
|
-
@click.argument(
|
|
1238
|
-
@click.option(
|
|
1239
|
-
def dvt_simulate_location_play(
|
|
1240
|
-
|
|
1241
|
-
|
|
1265
|
+
@dvt_simulate_location.command("play", cls=Command)
|
|
1266
|
+
@click.argument("filename", type=click.Path(exists=True, file_okay=True, dir_okay=False))
|
|
1267
|
+
@click.argument("timing_randomness_range", type=click.INT, default=0)
|
|
1268
|
+
@click.option("--disable-sleep", is_flag=True, default=False)
|
|
1269
|
+
def dvt_simulate_location_play(
|
|
1270
|
+
service_provider: LockdownClient, filename: str, timing_randomness_range: int, disable_sleep: bool
|
|
1271
|
+
) -> None:
|
|
1272
|
+
"""Simulate inputs from a given .gpx file"""
|
|
1242
1273
|
with DvtSecureSocketProxyService(service_provider) as dvt:
|
|
1243
|
-
LocationSimulation(dvt).play_gpx_file(
|
|
1244
|
-
|
|
1274
|
+
LocationSimulation(dvt).play_gpx_file(
|
|
1275
|
+
filename, disable_sleep=disable_sleep, timing_randomness_range=timing_randomness_range
|
|
1276
|
+
)
|
|
1245
1277
|
OSUTILS.wait_return()
|
|
1246
1278
|
|
|
1247
1279
|
|
|
1248
1280
|
@developer.group()
|
|
1249
1281
|
def core_device() -> None:
|
|
1250
|
-
"""
|
|
1282
|
+
"""Access features exposed by the DeveloperDiskImage"""
|
|
1251
1283
|
pass
|
|
1252
1284
|
|
|
1253
1285
|
|
|
1254
1286
|
async def core_device_list_directory_task(
|
|
1255
|
-
|
|
1287
|
+
service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str
|
|
1288
|
+
) -> None:
|
|
1256
1289
|
async with FileServiceService(service_provider, APPLE_DOMAIN_DICT[domain], identifier) as file_service:
|
|
1257
1290
|
print_json(await file_service.retrieve_directory_list(path))
|
|
1258
1291
|
|
|
1259
1292
|
|
|
1260
|
-
@core_device.command(
|
|
1261
|
-
@click.argument(
|
|
1262
|
-
@click.argument(
|
|
1263
|
-
@click.option(
|
|
1293
|
+
@core_device.command("list-directory", cls=RSDCommand)
|
|
1294
|
+
@click.argument("domain", type=click.Choice(APPLE_DOMAIN_DICT.keys()))
|
|
1295
|
+
@click.argument("path")
|
|
1296
|
+
@click.option("--identifier", default="")
|
|
1264
1297
|
def core_device_list_directory(
|
|
1265
|
-
|
|
1266
|
-
|
|
1298
|
+
service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str
|
|
1299
|
+
) -> None:
|
|
1300
|
+
"""List directory at given domain-path"""
|
|
1267
1301
|
asyncio.run(core_device_list_directory_task(service_provider, domain, path, identifier))
|
|
1268
1302
|
|
|
1269
1303
|
|
|
1270
1304
|
async def core_device_read_file_task(
|
|
1271
|
-
|
|
1272
|
-
|
|
1305
|
+
service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str, output: Optional[IO]
|
|
1306
|
+
) -> None:
|
|
1273
1307
|
async with FileServiceService(service_provider, APPLE_DOMAIN_DICT[domain], identifier) as file_service:
|
|
1274
1308
|
buf = await file_service.retrieve_file(path)
|
|
1275
1309
|
if output is not None:
|
|
@@ -1278,65 +1312,110 @@ async def core_device_read_file_task(
|
|
|
1278
1312
|
print(try_decode(buf))
|
|
1279
1313
|
|
|
1280
1314
|
|
|
1281
|
-
@core_device.command(
|
|
1282
|
-
@click.argument(
|
|
1283
|
-
@click.argument(
|
|
1284
|
-
@click.option(
|
|
1285
|
-
@click.option(
|
|
1315
|
+
@core_device.command("read-file", cls=RSDCommand)
|
|
1316
|
+
@click.argument("domain", type=click.Choice(APPLE_DOMAIN_DICT.keys()))
|
|
1317
|
+
@click.argument("path")
|
|
1318
|
+
@click.option("--identifier", default="")
|
|
1319
|
+
@click.option("-o", "--output", type=click.File("wb"))
|
|
1286
1320
|
def core_device_read_file(
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
"""
|
|
1321
|
+
service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str, output: Optional[IO]
|
|
1322
|
+
) -> None:
|
|
1323
|
+
"""Read file from given domain-path"""
|
|
1290
1324
|
asyncio.run(core_device_read_file_task(service_provider, domain, path, identifier, output))
|
|
1291
1325
|
|
|
1292
1326
|
|
|
1293
1327
|
async def core_device_propose_empty_file_task(
|
|
1294
|
-
|
|
1295
|
-
|
|
1328
|
+
service_provider: RemoteServiceDiscoveryService,
|
|
1329
|
+
domain: str,
|
|
1330
|
+
path: str,
|
|
1331
|
+
identifier: str,
|
|
1332
|
+
file_permissions: int,
|
|
1333
|
+
uid: int,
|
|
1334
|
+
gid: int,
|
|
1335
|
+
creation_time: int,
|
|
1336
|
+
last_modification_time: int,
|
|
1337
|
+
) -> None:
|
|
1296
1338
|
async with FileServiceService(service_provider, APPLE_DOMAIN_DICT[domain], identifier) as file_service:
|
|
1297
1339
|
await file_service.propose_empty_file(path, file_permissions, uid, gid, creation_time, last_modification_time)
|
|
1298
1340
|
|
|
1299
1341
|
|
|
1300
|
-
@core_device.command(
|
|
1301
|
-
@click.argument(
|
|
1302
|
-
@click.argument(
|
|
1303
|
-
@click.option(
|
|
1304
|
-
@click.option(
|
|
1305
|
-
@click.option(
|
|
1306
|
-
@click.option(
|
|
1307
|
-
@click.option(
|
|
1308
|
-
@click.option(
|
|
1342
|
+
@core_device.command("propose-empty-file", cls=RSDCommand)
|
|
1343
|
+
@click.argument("domain", type=click.Choice(APPLE_DOMAIN_DICT.keys()))
|
|
1344
|
+
@click.argument("path")
|
|
1345
|
+
@click.option("--identifier", default="")
|
|
1346
|
+
@click.option("--file-permissions", type=click.INT, default=0o644)
|
|
1347
|
+
@click.option("--uid", type=click.INT, default=501)
|
|
1348
|
+
@click.option("--gid", type=click.INT, default=501)
|
|
1349
|
+
@click.option("--creation-time", type=click.INT, default=time.time())
|
|
1350
|
+
@click.option("--last-modification-time", type=click.INT, default=time.time())
|
|
1309
1351
|
def core_device_propose_empty_file(
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1352
|
+
service_provider: RemoteServiceDiscoveryService,
|
|
1353
|
+
domain: str,
|
|
1354
|
+
path: str,
|
|
1355
|
+
identifier: str,
|
|
1356
|
+
file_permissions: int,
|
|
1357
|
+
uid: int,
|
|
1358
|
+
gid: int,
|
|
1359
|
+
creation_time: int,
|
|
1360
|
+
last_modification_time: int,
|
|
1361
|
+
) -> None:
|
|
1362
|
+
"""Write an empty file to given domain-path"""
|
|
1363
|
+
asyncio.run(
|
|
1364
|
+
core_device_propose_empty_file_task(
|
|
1365
|
+
service_provider,
|
|
1366
|
+
domain,
|
|
1367
|
+
path,
|
|
1368
|
+
identifier,
|
|
1369
|
+
file_permissions,
|
|
1370
|
+
uid,
|
|
1371
|
+
gid,
|
|
1372
|
+
creation_time,
|
|
1373
|
+
last_modification_time,
|
|
1374
|
+
)
|
|
1375
|
+
)
|
|
1315
1376
|
|
|
1316
1377
|
|
|
1317
1378
|
async def core_device_list_launch_application_task(
|
|
1318
|
-
|
|
1319
|
-
|
|
1379
|
+
service_provider: RemoteServiceDiscoveryService,
|
|
1380
|
+
bundle_identifier: str,
|
|
1381
|
+
argument: list[str],
|
|
1382
|
+
kill_existing: bool,
|
|
1383
|
+
suspended: bool,
|
|
1384
|
+
env: list[tuple[str, str]],
|
|
1385
|
+
) -> None:
|
|
1320
1386
|
async with AppServiceService(service_provider) as app_service:
|
|
1321
|
-
print_json(
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
@
|
|
1327
|
-
@click.argument(
|
|
1328
|
-
@click.
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1387
|
+
print_json(
|
|
1388
|
+
await app_service.launch_application(bundle_identifier, argument, kill_existing, suspended, dict(env))
|
|
1389
|
+
)
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
@core_device.command("launch-application", cls=RSDCommand)
|
|
1393
|
+
@click.argument("bundle_identifier")
|
|
1394
|
+
@click.argument("argument", nargs=-1)
|
|
1395
|
+
@click.option(
|
|
1396
|
+
"--kill-existing/--no-kill-existing", default=True, help="Whether to kill an existing instance of this process"
|
|
1397
|
+
)
|
|
1398
|
+
@click.option("--suspended", is_flag=True, help="Same as WaitForDebugger")
|
|
1399
|
+
@click.option(
|
|
1400
|
+
"--env",
|
|
1401
|
+
multiple=True,
|
|
1402
|
+
type=click.Tuple((str, str)),
|
|
1403
|
+
help="Environment variables to pass to process given as a list of key value",
|
|
1404
|
+
)
|
|
1333
1405
|
def core_device_launch_application(
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1406
|
+
service_provider: RemoteServiceDiscoveryService,
|
|
1407
|
+
bundle_identifier: str,
|
|
1408
|
+
argument: tuple[str],
|
|
1409
|
+
kill_existing: bool,
|
|
1410
|
+
suspended: bool,
|
|
1411
|
+
env: list[tuple[str, str]],
|
|
1412
|
+
) -> None:
|
|
1413
|
+
"""Launch application"""
|
|
1337
1414
|
asyncio.run(
|
|
1338
1415
|
core_device_list_launch_application_task(
|
|
1339
|
-
service_provider, bundle_identifier, list(argument), kill_existing, suspended, env
|
|
1416
|
+
service_provider, bundle_identifier, list(argument), kill_existing, suspended, env
|
|
1417
|
+
)
|
|
1418
|
+
)
|
|
1340
1419
|
|
|
1341
1420
|
|
|
1342
1421
|
async def core_device_list_processes_task(service_provider: RemoteServiceDiscoveryService) -> None:
|
|
@@ -1344,36 +1423,38 @@ async def core_device_list_processes_task(service_provider: RemoteServiceDiscove
|
|
|
1344
1423
|
print_json(await app_service.list_processes())
|
|
1345
1424
|
|
|
1346
1425
|
|
|
1347
|
-
@core_device.command(
|
|
1426
|
+
@core_device.command("list-processes", cls=RSDCommand)
|
|
1348
1427
|
def core_device_list_processes(service_provider: RemoteServiceDiscoveryService) -> None:
|
|
1349
|
-
"""
|
|
1428
|
+
"""Get process list"""
|
|
1350
1429
|
asyncio.run(core_device_list_processes_task(service_provider))
|
|
1351
1430
|
|
|
1352
1431
|
|
|
1353
|
-
async def core_device_uninstall_app_task(
|
|
1354
|
-
|
|
1432
|
+
async def core_device_uninstall_app_task(
|
|
1433
|
+
service_provider: RemoteServiceDiscoveryService, bundle_identifier: str
|
|
1434
|
+
) -> None:
|
|
1355
1435
|
async with AppServiceService(service_provider) as app_service:
|
|
1356
1436
|
await app_service.uninstall_app(bundle_identifier)
|
|
1357
1437
|
|
|
1358
1438
|
|
|
1359
|
-
@core_device.command(
|
|
1360
|
-
@click.argument(
|
|
1439
|
+
@core_device.command("uninstall", cls=RSDCommand)
|
|
1440
|
+
@click.argument("bundle_identifier")
|
|
1361
1441
|
def core_device_uninstall_app(service_provider: RemoteServiceDiscoveryService, bundle_identifier: str) -> None:
|
|
1362
|
-
"""
|
|
1442
|
+
"""Uninstall application"""
|
|
1363
1443
|
asyncio.run(core_device_uninstall_app_task(service_provider, bundle_identifier))
|
|
1364
1444
|
|
|
1365
1445
|
|
|
1366
1446
|
async def core_device_send_signal_to_process_task(
|
|
1367
|
-
|
|
1447
|
+
service_provider: RemoteServiceDiscoveryService, pid: int, signal: int
|
|
1448
|
+
) -> None:
|
|
1368
1449
|
async with AppServiceService(service_provider) as app_service:
|
|
1369
1450
|
print_json(await app_service.send_signal_to_process(pid, signal))
|
|
1370
1451
|
|
|
1371
1452
|
|
|
1372
|
-
@core_device.command(
|
|
1373
|
-
@click.argument(
|
|
1374
|
-
@click.argument(
|
|
1453
|
+
@core_device.command("send-signal-to-process", cls=RSDCommand)
|
|
1454
|
+
@click.argument("pid", type=click.INT)
|
|
1455
|
+
@click.argument("signal", type=click.INT)
|
|
1375
1456
|
def core_device_send_signal_to_process(service_provider: RemoteServiceDiscoveryService, pid: int, signal: int) -> None:
|
|
1376
|
-
"""
|
|
1457
|
+
"""Send signal to process"""
|
|
1377
1458
|
asyncio.run(core_device_send_signal_to_process_task(service_provider, pid, signal))
|
|
1378
1459
|
|
|
1379
1460
|
|
|
@@ -1382,9 +1463,9 @@ async def core_device_get_device_info_task(service_provider: RemoteServiceDiscov
|
|
|
1382
1463
|
print_json(await app_service.get_device_info())
|
|
1383
1464
|
|
|
1384
1465
|
|
|
1385
|
-
@core_device.command(
|
|
1466
|
+
@core_device.command("get-device-info", cls=RSDCommand)
|
|
1386
1467
|
def core_device_get_device_info(service_provider: RemoteServiceDiscoveryService) -> None:
|
|
1387
|
-
"""
|
|
1468
|
+
"""Get device information"""
|
|
1388
1469
|
asyncio.run(core_device_get_device_info_task(service_provider))
|
|
1389
1470
|
|
|
1390
1471
|
|
|
@@ -1393,22 +1474,22 @@ async def core_device_get_display_info_task(service_provider: RemoteServiceDisco
|
|
|
1393
1474
|
print_json(await app_service.get_display_info())
|
|
1394
1475
|
|
|
1395
1476
|
|
|
1396
|
-
@core_device.command(
|
|
1477
|
+
@core_device.command("get-display-info", cls=RSDCommand)
|
|
1397
1478
|
def core_device_get_display_info(service_provider: RemoteServiceDiscoveryService) -> None:
|
|
1398
|
-
"""
|
|
1479
|
+
"""Get display information"""
|
|
1399
1480
|
asyncio.run(core_device_get_display_info_task(service_provider))
|
|
1400
1481
|
|
|
1401
1482
|
|
|
1402
1483
|
async def core_device_query_mobilegestalt_task(service_provider: RemoteServiceDiscoveryService, key: list[str]) -> None:
|
|
1403
|
-
"""
|
|
1484
|
+
"""Query MobileGestalt"""
|
|
1404
1485
|
async with DeviceInfoService(service_provider) as app_service:
|
|
1405
1486
|
print_json(await app_service.query_mobilegestalt(key))
|
|
1406
1487
|
|
|
1407
1488
|
|
|
1408
|
-
@core_device.command(
|
|
1409
|
-
@click.argument(
|
|
1489
|
+
@core_device.command("query-mobilegestalt", cls=RSDCommand)
|
|
1490
|
+
@click.argument("key", nargs=-1, type=click.STRING)
|
|
1410
1491
|
def core_device_query_mobilegestalt(service_provider: RemoteServiceDiscoveryService, key: tuple[str]) -> None:
|
|
1411
|
-
"""
|
|
1492
|
+
"""Query MobileGestalt"""
|
|
1412
1493
|
asyncio.run(core_device_query_mobilegestalt_task(service_provider, list(key)))
|
|
1413
1494
|
|
|
1414
1495
|
|
|
@@ -1417,9 +1498,9 @@ async def core_device_get_lockstate_task(service_provider: RemoteServiceDiscover
|
|
|
1417
1498
|
print_json(await app_service.get_lockstate())
|
|
1418
1499
|
|
|
1419
1500
|
|
|
1420
|
-
@core_device.command(
|
|
1501
|
+
@core_device.command("get-lockstate", cls=RSDCommand)
|
|
1421
1502
|
def core_device_get_lockstate(service_provider: RemoteServiceDiscoveryService) -> None:
|
|
1422
|
-
"""
|
|
1503
|
+
"""Get lockstate"""
|
|
1423
1504
|
asyncio.run(core_device_get_lockstate_task(service_provider))
|
|
1424
1505
|
|
|
1425
1506
|
|
|
@@ -1428,9 +1509,9 @@ async def core_device_list_apps_task(service_provider: RemoteServiceDiscoverySer
|
|
|
1428
1509
|
print_json(await app_service.list_apps())
|
|
1429
1510
|
|
|
1430
1511
|
|
|
1431
|
-
@core_device.command(
|
|
1512
|
+
@core_device.command("list-apps", cls=RSDCommand)
|
|
1432
1513
|
def core_device_list_apps(service_provider: RemoteServiceDiscoveryService) -> None:
|
|
1433
|
-
"""
|
|
1514
|
+
"""Get application list"""
|
|
1434
1515
|
asyncio.run(core_device_list_apps_task(service_provider))
|
|
1435
1516
|
|
|
1436
1517
|
|
|
@@ -1438,20 +1519,21 @@ async def core_device_sysdiagnose_task(service_provider: RemoteServiceDiscoveryS
|
|
|
1438
1519
|
output = Path(output)
|
|
1439
1520
|
async with DiagnosticsServiceService(service_provider) as service:
|
|
1440
1521
|
response = await service.capture_sysdiagnose(False)
|
|
1441
|
-
logger.info(f
|
|
1522
|
+
logger.info(f"Operation response: {response}")
|
|
1442
1523
|
if output.is_dir():
|
|
1443
1524
|
output /= response.preferred_filename
|
|
1444
|
-
logger.info(f
|
|
1525
|
+
logger.info(f"Downloading sysdiagnose to: {output}")
|
|
1445
1526
|
|
|
1446
1527
|
# get the file over lockdownd which is WAYYY faster
|
|
1447
1528
|
lockdown = create_using_usbmux(service_provider.udid)
|
|
1448
1529
|
with CrashReportsManager(lockdown) as crash_reports_manager:
|
|
1449
|
-
crash_reports_manager.afc.pull(
|
|
1450
|
-
|
|
1530
|
+
crash_reports_manager.afc.pull(
|
|
1531
|
+
posixpath.join(f"/DiagnosticLogs/sysdiagnose/{response.preferred_filename}"), output
|
|
1532
|
+
)
|
|
1451
1533
|
|
|
1452
1534
|
|
|
1453
|
-
@core_device.command(
|
|
1454
|
-
@click.argument(
|
|
1535
|
+
@core_device.command("sysdiagnose", cls=RSDCommand)
|
|
1536
|
+
@click.argument("output", type=click.Path(dir_okay=True, file_okay=True, exists=True))
|
|
1455
1537
|
def core_device_sysdiagnose(service_provider: RemoteServiceDiscoveryService, output: str) -> None:
|
|
1456
|
-
"""
|
|
1538
|
+
"""Execute sysdiagnose and fetch the output file"""
|
|
1457
1539
|
asyncio.run(core_device_sysdiagnose_task(service_provider, output))
|