自动化工具 硬货来啦!!使用纯 python 实现 Instruments 协议,跨平台 (win,mac,linux) 获取 iOS 性能数据

YueChen · 2020年12月15日 · 最后由 小锤 回复于 2024年05月04日 · 35881 次阅读
本帖已被设为精华帖!

前言

获取 iOS 性能数据,一直都是比较麻烦的事情,之前在构建测试框架&平台的时候,获取 iOS 性能也是没有什么好的办法,也只能使用比较原始的方式,获取非越狱 iOS 性能数据,例如 xcode 调试可以获取,Instruments 获取数据,用起来也是极为不便。

到现在能做到跨平台,对非越狱 iOS 机器进行监控数据的貌似只有腾讯一家 PerfDog,确实非常优秀,但是腾讯对于 PerfDog 的技术还是比较保密的,GitHub 上比较有名的关于 iOS 设备控制仓库上例如 facebook idb,libimobiledevice 等,也都没有获取性能数据相关内容。

目前已经实现

使用纯 Python 代码跨平台 (win,mac,linux) 获取 iOS 部分性能数据&其他数据,10.3 < iOS version < 14.2 虽然可以获取绝大部分 iOS 设备性能数据,当然也有部分设备没有兼容,也希望有兴趣的同学可以一起探讨共同开发。最终开源项目地址会在文章末尾开放

基本工作原理介绍

  • Usbmux
  • Lcokdown
  • LaunchDaemon
  • Instruments 通道
  • Framework
    • DeveloperDiskImage.dmg
    • MobileDevice.framework
  • DTXMessage 流

Usbmux

usbmuxd 的主要作用就是,通过 usb 来构建一条 socket 通道来实现 Mac OS 与 iOS 之间的通讯

windows 上则是 C:\Program Files\Common Files\Apple\Mobile Device Support\AppleMobileDeviceService.exe 驱动来监听 27105 端口与 iOS 之间进行通讯

想做相关工具开发,获取数据显然必不可少,下面监听 usbmuxd

### 转移 usbmuxd
sudo  mv /var/run/usbmuxd /var/run/usbmuxx
### 监听 usbmuxd 
sudo socat -t100 -x -v UNIX-LISTEN:/var/run/usbmuxd,mode=777,reuseaddr,fork UNIX-CONNECT:/var/run/usbmuxx 

### socat 工具是以文本来展示的,看起来相当费劲,
### 通过管道转到 wiershark 上面,wireshark 查看 usbmuxd 
sudo socat -d -d -lf /dev/stdout -x -v 2>&1   UNIX-LISTEN:/var/run/usbmuxd,mode=777,reuseaddr,fork UNIX-CONNECT:/var/run/usbmuxx | awk '/^[<>]/{a=0; $1 == "<" ? "I" : "0"; next}{$0 = substr($0, 1, 48);printf "%.4x %s\n",a,$0;a+=NF}' | text2pcap  - -  | wireshark  -ki -

### 很重要 !!!用完之后必须要复位,不然你的电脑之后就找不到手机了,必须重启电脑才行
sudo  mv /var/run/usbmuxx /var/run/usbmuxd

LaunchDaemon & Lcokdown

用于与设备配对并启动其他服务。
成功配对后才能访问其他服务。成功配对需要解锁设备,并且用户单击手机屏幕上的 “信任此设备”。后面主要都是与 Lcokdown 服务进行交互通讯

主要交互方式通过 plist 格式文件进行交互来启动相关服务,这里咱们使用 python plistlib 来进行 byte 流转换

例如启动 com.apple.syslog_relay 服务

2020-12-15 14:04:53,987 - plist_service.py[line:88] - DEBUG: 发送 Plist: {'Request': 'StartService', 'Label': 'pyMobileDevice', 'Service': 'com.apple.syslog_relay'}
2020-12-15 14:04:53,987 - plist_service.py[line:90] - DEBUG: 发送 Plist byte: b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n\t<key>Label</key>\n\t<string>pyMobileDevice</string>\n\t<key>Request</key>\n\t<string>StartService</string>\n\t<key>Service</key>\n\t<string>com.apple.syslog_relay</string>\n</dict>\n</plist>\n'

Instruments 服务

想要与 Instruments 服务进行通讯,第一个我们需要找到入口,第二个我们需要知道交换 DTXMessage 数据流格式,并对其进行编解码

这里如何开启通道,以及数据解析,就要看向Troy Bowman 大佬在逆向工程大会上发布的演讲。

大佬的两个项目连接共大家参考

https://github.com/troybowman/dtxmsg

https://github.com/troybowman/ios_instruments_client

好了知道相关流程了,开始抄作业吧😄 😄
DTXMessage Header 头解析
https://github.com/troybowman/ios_instruments_client/blob/master/ios_instruments_client.h
https://github.com/facebook/idb/tree/master/PrivateHeaders IDB 仓库也有大量不同协议头,有兴趣的可自行查看,使用 python 构造 DTXMessage 头解析协议头示例

class DTXMessageHeader(Structure):
    _fields_ = [
        ('magic', c_uint32),
        ('cb', c_uint32),
        ('fragmentId', c_uint16),
        ('fragmentCount', c_uint16),
        ('length', c_uint32),
        ('identifier', c_uint32),
        ('conversationIndex', c_uint32),
        ('channelCode', c_uint32),
        ('expectsReply', c_uint32)
    ]


class DTXMessagePayloadHeader(Structure):
    _fields_ = [
        ('flags', c_uint32),
        ('auxiliaryLength', c_uint32),
        ('totalLength', c_uint64)
    ]

如何拿到 dtx 流数据可以参考项目:
https://github.com/danielpaulus/dtx_codec
该项目可以直接获取和模拟器进行交互时的 dtx ,可以用来测试数据解析是否正常。

sysmontap.py 示例效果展示

[
    {
        "PerCPUUsage": [
            {
                "CPU_NiceLoad": 0.0,
                "CPU_SystemLoad": -1.0,
                "CPU_TotalLoad": 11.881188118811878,
                "CPU_UserLoad": -1.0
            },
            {
                "CPU_NiceLoad": 0.0,
                "CPU_SystemLoad": -1.0,
                "CPU_TotalLoad": 17.0,
                "CPU_UserLoad": -1.0
            }
        ],
        "EndMachAbsTime": 656566442146,
        "CPUCount": 2,
        "EnabledCPUs": 2,
        "SystemCPUUsage": {
            "CPU_NiceLoad": 0.0,
            "CPU_SystemLoad": -1.0,
            "CPU_TotalLoad": 28.881188118811878,
            "CPU_UserLoad": -1.0
        },
        "Type": 33,
        "StartMachAbsTime": 656542341717
    },
    {
        "Processes": {
            "351": [
                351,            // pid 
                417710325760,   // memVirtualSize
                770048,         // memResidentSize
                0.0,            // cpuUsage
                528,
                -82,            
                934232,         // physFootprint
                819200,         // memAnon
                0.0,            // powerScore
                708608          // diskBytesRead
            ],
            "519": [
                519,
                418581921792,
                46628864,
                13.8574323237612,
                30281,
                6465,
                61965152,
                20381696,
                14.082756426586586,
                57790464
            ],
            "311": [
                311,
                417748434944,
                6635520,
                0.0,
                10189,
                43,
                1671552,
                1540096,
                0.0,
                22274048
            ],
            "271": [
                271,
                417744961536,
                4718592,
                0.0,
                8188,
                473,
                2130344,
                1998848,
                0.0,
                36442112
            ]
        },
        "Type": 5,
        "EndMachAbsTime": 656567535862,
        "StartMachAbsTime": 656542716738
    }
]

使用时注意事项及需求:

  • 电脑需要有 iOS 驱动 iTunes,Linux 安装 usbmuxd 驱动自行百度
  • 如果是 mac 可能没有 lockdown 权限,需要 sudo chmod 777 /var/db/lockdown/
  • 如果抛出 StartServiceError 异常,需要使用 xcode 激活一下设备,激活方式打开 xcode ,插上手机点击 “信任”,即可拔掉数据线。(理论上这一步也可以用脚本进行激活,暂未实现)

本项目源码地址:

https://github.com/YueChen-C/py-ios-device
既然看完了,要是能点个 star 就最好啦

本项目基于
https://github.com/iOSForensics/pymobiledevice 因为项目比较久,在项目基础上进行优化和修改并增加了 instruments 协议

参考项目:
https://github.com/troybowman/dtxmsg
https://github.com/troybowman/ios_instruments_client
https://github.com/danielpaulus/dtx_codec
https://github.com/libimobiledevice
https://github.com/facebook/idb

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 51 条回复 时间 点赞

这个真的有点厉害。。。。

厉害,马上入手研究

5楼 已删除

转移 usbmuxd
这一步操作是为了什么?

想参与 怎么联系大佬

剪烛 回复

这个仅仅是为了抓包分析数据用的。正常情况不需要这样

李鹏 回复

有兴趣共同研究可加 v:cpj1352

卡斯 将本帖设为了精华贴 12月17日 14:49
YueChen 回复

已经添加 wx,感谢

执行:如果是 mac 可能没有 lockdown 权限,需要
sudo chmod 777 /var/db/lockdown/
这可能提示无法修改,即便是使用了 sudo。
在 macOS 10.10 之后的系统需要先关闭内核保护才可。
重启 mac 电脑的时候按住 Command+R,进入恢复模式后(可能需要选择一个账户,再点击下一步)
状态栏:实用工具 - 终端,输入命令:
csrutil disable
reboot
注意:电脑升级啥的可能导致重新锁上,需要重复该步骤


老铁加油 !!!

李鹏 回复

新手一枚。有两个问题请教一下。
1、如何获取 APP NAME?get_processes 能返回所有进程和 name,我如果需要获取其中一个,我怎么获取对应的 appname
2、get_performance_by_process_id ;get_performance_by_app_name 其中需要回调函数作为参数,这个具体怎么用呢?回调函数是自己写一个么

YY 回复

这个直接查看源码 demo 里面有使用方法,问题 2 这种是其他小伙伴封装的使用方法

大佬,请教一下。我们这儿有个测试手机,之前运行都正常,但是突然开始出现以下错误,并且重启也无法恢复(换手机正常),请问遇见过吗?

2021-01-06 10:28:16,406 - lockdown.py[line:75] - ERROR: [Errno 13] Permission denied: '/var/db/lockdown/00008020-001E358234EA002E.plist'
Traceback (most recent call last):
  File "/Users/515/code/py-ios-device-main/instrument/RPC.py", line 214, in init
    self._cli = self.lockdown.start_service("com.apple.instruments.remoteserver")
  File "/Users/515/code/py-ios-device-main/util/lockdown.py", line 182, in start_service
    raise StartServiceError(f'Unable to start service={name!r} - {error}')
util.exceptions.StartServiceError: Unable to start service='com.apple.instruments.remoteserver' - SessionInactive

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/515/code/py-ios-device-main/demo/instrument_services/__init__.py", line 12, in <module>
    rpc = get_usb_rpc()
  File "/Users/515/code/py-ios-device-main/instrument/RPC.py", line 20, in get_usb_rpc
    if not rpc.init(DTXUSBTransport):
  File "/Users/515/code/py-ios-device-main/instrument/RPC.py", line 219, in init
    self._cli = self.lockdown.start_service("com.apple.instruments.remoteserver.DVTSecureSocketProxy")
  File "/Users/515/code/py-ios-device-main/util/lockdown.py", line 182, in start_service
    raise StartServiceError(f'Unable to start service={name!r} - {error}')
util.exceptions.StartServiceError: Unable to start service='com.apple.instruments.remoteserver.DVTSecureSocketProxy' - SessionInactive
剪烛 回复

看文章里这条,原因我也未知,有时候这个服务就会挂掉,需要连接下 xcode 或者 instruments 工具就能用了,instruments 挂掉了尝试激活 instruments 服务脚本我也在研究

如果抛出 StartServiceError 异常,需要使用 xcode 激活一下设备,激活方式打开 xcode ,插上手机点击 “信任”,即可拔掉数据线。(理论上这一步也可以用脚本进行激活,暂未实现)

YueChen 回复

但是奇怪就是,在出现这个错误的同时,Instruments 是可以正常获取数据的。我们尝试了很多方法,完全没办法,最后换手机了。orz 救命

增加一个 unix_socket.py 小工具,模拟 socat 中间人代理, 直接监听 iOS 与 usbmuxd 传输,可将数据包自动转换成明文数据,使用方法看 unix_socket.py

@YueChen 以前写的一个工具,经过层层审批终于开源了,多亏了你这个项目 https://github.com/alibaba/taobao-iphone-device

codeskyblue 回复

@codeskyblue 大佬终于开源了呀 ,最近也抽时间在看 xctest 相关,看大佬项目模拟器已经搞完了,赶紧去膜拜下的代码😝

YueChen 回复

我是拿真机搞的

codeskyblue 回复

大佬牛逼,真机试了下可以启动 wda 👍

YueChen 回复

是不是爽的飞起,终于可以不用 xcodebuild 了

大佬~可以 pypi 上求个更新吗~

剪烛 回复

最近楼主大佬加了很多功能,Python 库要晚一点哈

剪烛 回复

Python 库做完了.
使用文档: 查看文档

使用 demo: 查看 demo

请教下关于 app 的耗电量,电流,电压之类的性能如何拿到

git 中 demo 获取单个应用 activity 数据 直接运行 没有结果展示 是什么原因

_YI 回复

这个方法只能支持 11 以下设备

YueChen 回复

thx

正式工作中,这些数据拿到,去做什么,能不能促进项目的发展

大佬的东西是真实用 最近学习了 tidevice 源码 在尝试 用 socat 映射 usbmux 直接远程 直接获取信息

管昭 回复

你获取这些性能指标,工作中做什么用啊。请教下

回复

一般都是配合某些专项测试,竞品测试,自动化脚本这三个来使用收集监测数据。
这样问感觉就跟问 adb 能干嘛 Instruments 能干嘛一样。主要还是看业务到底想做啥吧?

YueChen 回复

是的,我们也在探索,adb 这种命令实际工作中能对项目有什么促进作用。但是感觉就是竞品测试,对比下跟竞品的差距

py-ios-device 我用的 java 版本,代码已经修改了包名和 UUID,还是运行错误?

阿三 回复

修改了下可以测试看看

42楼 已删除
YueChen 回复

报错 1:log4j:ERROR ssl Handshake error

报错 2: 需要什么证书?

wifi 连接,init_wireless 这个是咋个用。,另外截屏那个图片如何发送到计算机?

pyidevice instruments 这个必须在 mac 环境上操作吗,在 windows 上敲命令报错,看上去像 instruments 无法启动

疯疯 回复

你这应该是 GitHub 下载镜像有问题,你手动下载一个,放到 C:/Users/xxxxx/.py_ios_device/
https://github.com/JinjunHan/iOSDeviceSupport/tree/master/DeviceSupport/

YueChen 回复

多谢大佬,问题解决了,还想请教下 “设置模拟真机设备高压过热状态” 这个模拟功能是 pyidevice instruments 哪个命令

感谢分享,最近将部分实现移植到了 gidevice
https://github.com/electricbubble/gidevice/pull/40

请教下大佬,为什么我的 pyidevice instruments appmonitor 命令不存在,pyidevice 版本是 2.3.32.1

绫小祖 回复

额不应该把,我看应该是有的,是不是版本用错了,我试了最近版本应该没问题。

大佬请教一下为什么在 ios16 上通过 com.apple.instruments.server.services.coreprofilesessiontap 服务请求内核数据流时,只能得到很短时间内的内核数据流,之后就会卡死,没有任何输出

大佬求助一下,关于刚更新的 iOS17。
iOS17 对应的 Xcode15 Beta 里没有给出 DeviceSupport 包,现在有办法采集 iOS17 设备的数据吗?

YueChen 回复

大佬,这个小工具在哪里呢?源码中没有找到呢

管昭 回复

大佬实现了吗,可以交流一下吗

需要一个 windows 端,有没有大佬接单的?条件优厚

小锤 回复

我有现成的怎么联系

57楼 已删除
ZhangIrving 回复

看我主页有联系方式

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册