简介:面向 iOS 17 及以上系统、希望在免越狱条件下研究虚拟定位与模拟跑步打卡的开发者与进阶用户,提供一套跨平台的命令行脚本工具,支持在线拾取路径,并可对跑步距离、时间、速度等参数进行设置,便于搭建测试环境或验证定位轨迹方案。压缩包共 16 个文件,约 15KB,以 9 个 Python 脚本为核心,配合 yaml/yml 配置、txt 路径数据、md 说明及 license 等文件,整体轻量,侧重源码与配置参考而非成品安装包。已有 716 人学习,说明该方向具备一定关注度。读者可从中了解主程序入口、设备连接、定位注入、路径处理与隧道连接等模块的组织方式,并借助示例路径与配置理解在线拾取和参数调整流程,适合按模块拆解学习、二次修改与排错参考。需注意虚拟定位可能违反部分运动应用条款,建议仅用于技术研究与合规测试场景。
1. 一台刚升到 iOS 17 的机器:连上了,但就是定不了位
数据线插上,usbmux list能把 UDID 列出来,lockdownd info也能把设备型号、系统版本打印得清清楚楚,可一到执行定位命令那一步,终端就停在那儿不动了。这个场景我在做地图类 App 的轨迹联调时遇到过不止一次。iOS 17 之后,免越狱的 iOS 17 虚拟定位多了一层隧道,跨平台支持又多了一层驱动,两个坑叠在一起,很容易让人误判成「工具不行」。这套环境适合需要在真机上回放跑步轨迹、验证运动类界面跟随、调试地图定位漂移的开发者。装不好的典型表现是:设备连得上、开发者镜像挂不上、隧道起不来,折腾一下午最后靠重装系统来治。
2. 环境准备:把四层依赖拆开看,再按平台装
很多人装这套东西翻车,不是因为命令敲错,是因为没搞清楚自己在装哪一层。设备到电脑之间不是一条线,是四层叠起来的:USB 通信层负责「看见设备」,配对层负责「被设备信任」,隧道层负责「iOS 17 之后的高权限通道」,命令层负责「把坐标发过去」。四层任何一层缺,症状都长得很像——命令卡住、报个含糊的错、或者干脆静默失败。所以这一章先把四层摊开,再给三个平台的安装路径。
2.1 四层组件各自负责什么
第一层是 USB 通信。设备插上去之后,系统里需要一个常驻的 usbmux 服务,把 USB 上的连接转成本地的一个套接字(默认在/var/run/usbmuxd或 Windows 上的命名管道)。macOS 自带,Linux 要靠usbmuxd这个系统服务,Windows 则依赖配套的移动设备支持组件,没有它连设备列表都是空的。
第二层是配对与信任。设备第一次连电脑会弹「信任此电脑」,点过之后会在设备上留一份配对记录,电脑侧也存一份。这份记录决定了后面所有服务能不能起。重装系统、换了 USB 口、换了一台电脑,都可能让配对失效。
第三层是隧道,这是 iOS 17 之后新增的门槛。iOS 17 起,开发者服务(DVT)不再走普通的 lockdownd 通道,而是走一条基于 RemoteXPC 的加密隧道。这条隧道需要 root 或管理员权限去建虚拟网卡,Windows 上还要额外装一个轻量网卡驱动。隧道没起,后面所有developer dvt开头的命令都会超时。
第四层是命令层,也就是pymobiledevice3这个 Python 工具本身。它封装了前面三层,把「建隧道、连服务、发定位」串成几条子命令。它装在哪里、用哪个 Python 解释器跑,会直接影响权限能不能继承到——这一点后面第 5 章会专门讲。
提示:排查时永远从第一层往上查,不要一上来就怀疑命令层。设备列表出不来,问题一定在第一层或第二层;设备列表正常但隧道超时,问题在第三层。
2.2 三个平台的依赖对照与安装命令
先把环境对照表列出来,按自己的平台对号入座。这张表是我自己在三台机器上分别装过之后整理的,凡是标「需额外装」的,都是不带就会卡住的那种。
| 平台 | USB 通信层 | 隧道依赖 | 权限要求 | 备注 |
|---|---|---|---|---|
| macOS | 系统自带 usbmuxd | 系统自带虚拟网卡 | 建隧道需 sudo | 最省事,推荐主力机用 |
| Linux | 需装 usbmuxd 服务 | 需内核支持 tun | 建隧道需 root | 还要配 udev 规则,否则普通用户没权限 |
| Windows | 需装移动设备支持组件 | 需装轻量网卡驱动 | 管理员终端 | 驱动没装会报「找不到设备」 |
macOS 上的安装最干脆,用 pipx 做隔离安装,避免污染系统 Python:
# 装 pipx(如果还没有) brew install pipx pipx ensurepath # 用 pipx 隔离安装命令层工具 pipx install pymobiledevice3 # 确认装好,能看到子命令列表就说明第四层通了 pymobiledevice3 --helppipx的作用是给每个命令行工具单独建一个虚拟环境,同时又把它软链到~/.local/bin里,这样终端能直接调,又不会和系统自带的 Python 包打架。ensurepath是把它写进 shell 的 PATH,执行完要重开一个终端窗口才生效——这一步漏掉,后面会出现「明明装了却 command not found」。
Linux 上多两步,usbmuxd 服务和 udev 规则:
# 装系统级的 USB 通信服务和基础库 sudo apt install -y usbmuxd libimobiledevice-utils libusbmuxd-tools # 启动服务并设为开机自启 sudo systemctl enable --now usbmuxd # 加 udev 规则,让普通用户也能访问设备节点 sudo tee /etc/udev/rules.d/39-ios-device.rules >/dev/null <<'EOF' SUBSYSTEM=="usb", ATTR{idVendor}=="05ac", MODE="0666", GROUP="plugdev" EOF sudo udevadm control --reload-rules && sudo udevadm trigger这段规则的意思是:把所有 USB 子系统里厂商 ID 匹配的设备节点权限放宽到 0666,并归到 plugdev 组。改完必须reload-rules加trigger重扫一遍,否则规则只对下一次插拔生效。装完先用idevice_id -l验证一下,能列出 UDID 就说明前两层通了。
Windows 上不用命令行装系统组件,走图形界面:装好移动设备支持组件(一般随设备配套工具一起下发),再装轻量网卡驱动并把程序放到系统目录,然后用管理员权限打开 PowerShell:
# 建独立虚拟环境,避免和系统 Python 冲突 py -3 -m venv $env:USERPROFILE\.venvs\iosloc & $env:USERPROFILE\.venvs\iosloc\Scripts\Activate.ps1 # 在虚拟环境里装命令层 python -m pip install -U pip python -m pip install -U pymobiledevice3 # 验证 pymobiledevice3 --helpPowerShell 里执行Activate.ps1可能被默认执行策略拦住,报「无法加载文件,因为在此系统上禁止运行脚本」。用Set-ExecutionPolicy -Scope CurrentUser RemoteSigned放开当前用户即可,不要动机器级别的策略。
2.3 Python 环境隔离:别把工具装进系统解释器
这一节单独拎出来说,是因为它导致的故障最难认。用pip install直接装到系统 Python 里,在 macOS 上会撞上「externally-managed-environment」直接失败;在 Linux 上装成功了,但sudo一加,PATH 就换成了 root 的环境,命令又找不到了。
我一般两个方案二选一:pipx(推荐,命令行工具专用)或者手动 venv。手动 venv 的好处是路径完全由自己控制,后面sudo的时候能写全路径绕过 PATH 问题:
# 在用户目录下建一个专用环境 python3 -m venv ~/.venvs/iosloc source ~/.venvs/iosloc/bin/activate # 装完之后记下这个绝对路径,sudo 场景要用 which pymobiledevice3 # 输出类似 /home/you/.venvs/iosloc/bin/pymobiledevice3记下这个绝对路径很关键。因为隧道必须用 root 起,而sudo pymobiledevice3在 venv 里几乎必然失败——sudo默认重置 PATH,找不到 venv 里的可执行文件。正确写法是sudo ~/.venvs/iosloc/bin/pymobiledevice3 ...,把路径写死。
注意:不要图省事用
sudo pip install。装到 root 环境里,后面每个命令都得 sudo,权限问题会被放大成一片乱麻。
3. 配对、开发者模式与镜像挂载:iOS 17 卡得最狠的三步
装完工具只是拿到了钥匙,门还得一道道开。iOS 17 上这三步的顺序不能乱:先配对,再开开发者模式,最后挂开发者镜像。顺序错了会得到一堆看起来毫不相关的报错,比如「服务未找到」「设备未就绪」,实际上根因都在上一步。
3.1 配对与信任:先把设备列表跑出来
第一步永远是确认设备可见。插上线,设备解锁,屏幕上弹出「信任此电脑」时点信任并输密码。然后执行:
# 列出当前连接的设备,输出 UDID pymobiledevice3 usbmux list # 读取设备基础信息,验证配对是否有效 pymobiledevice3 lockdownd infousbmux list走的是第一层的 usbmux 服务,只要驱动和线没问题就能出结果。lockdownd info走的是第二层,需要配对记录有效,输出里能看到ProductVersion、BuildVersion、DeviceName这些字段。如果第一条有输出、第二条报错,说明配对坏了,去设备上「通用 → 传输或还原 → 还原位置与隐私」重置一下信任关系,重新插拔。
多条设备同时连着的时候,后面所有命令都要带--udid。别指望它自动选,多设备场景下自动选的那台经常不是你正在看的那台,定位打上去人却一脸懵。
3.2 开发者模式:两条开启路径
iOS 16 之后,开发者模式是一个独立开关,藏在「设置 → 隐私与安全性 → 开发者模式」里,默认不显示。让它显示出来有两条路。
第一条是官方路径:把设备连到开发者工具的设备面板上,工具会主动下发开发者模式的能力,设备上那个菜单项就出现了。这条路径最稳,但要求你手边有完整的开发工具链。
第二条是命令行路径,适合只有一台干净的笔记本、不想装几个 G 开发工具的场景:
# 请求设备开启开发者模式(下发的是能力,不是开关本身) pymobiledevice3 amfi enable-developer-mode # 执行完设备会要求重启,重启后到设置里手动打开开关amfi是设备上的一个服务名,这条命令的作用是让系统认可「这台设备要用于开发」。执行完设备会提示重启,重启之后再到设置里把开关拨开,系统会要求输一次锁屏密码。开关打开后设备会再重启一次,这是正常现象,不是卡死。
提示:开发者模式打开后会略微降低系统的安全校验强度,日常主力机不建议长期开着。测试机开,测完关掉,是我自己的习惯。
验证是否真的开了,看这条命令的返回:
pymobiledevice3 amfi developer-mode-status返回true才算成。返回false或者直接报服务不存在,说明开关没生效,回去重走一遍重启流程。
3.3 开发者镜像:版本必须和设备对得上
开发者镜像是设备端跑开发者服务时挂载的一个磁盘映像,里面是 DVT 相关的一堆服务。iOS 17 之后,镜像不挂上,developer dvt开头的命令全部会超时——注意是超时,不是报错,这个特征很容易让人以为是网络问题。
# 自动匹配并挂载与当前设备版本对应的镜像 pymobiledevice3 mounter auto-mount # 查看已挂载的镜像列表,确认状态 pymobiledevice3 mounter listauto-mount会自动从本地缓存里找匹配当前BuildVersion的镜像,找不到就下载。这里有个坑:它匹配的是构建号(比如21A329)而不是版本号(17.0),同一版本号下不同小版本构建号不同,镜像不能混用。所以设备一升级系统,就得重新挂一次。
mounter list的输出里要能看到一条状态是已挂载的记录。如果列表是空的,重复执行auto-mount并观察输出,通常会看到下载进度或者明确的失败原因。
Linux 上这一步还额外需要libimobiledevice-utils里的ideviceimagemounter做兜底,某些发行版的 usbmuxd 版本偏老,auto-mount会挂不上,用系统工具手动挂一次再回来跑命令层。
4. 起隧道与注入定位:iOS 17 真正的分水岭
前三步做完,设备已经「听话」了,但还没到能发坐标的程度。iOS 17 把开发者服务挪进了一条加密隧道,这条隧道是整篇文章里最容易翻车的地方,也是免越狱方案能在 iOS 17 上继续跑的关键。理解它为什么存在,比记住命令有用得多。
4.1 隧道到底解决了什么问题
早期的方案是直接通过 usbmux 转发的端口连到设备上的开发者服务,简单但要暴露一个本地端口,安全性一般。iOS 17 之后改成了 RemoteXPC:电脑侧建一个虚拟网卡,和设备之间拉一条点对点的加密链路,链路两端各有一个地址和端口,叫 RSD(Remote Service Discovery)。所有开发者服务都从这条链路里找,不再走 USB 端口转发。
这就解释了两个现象。一是为什么必须 root:建虚拟网卡是系统级操作。二是为什么隧道进程必须一直挂着:它不是一次性的握手,是后续所有命令的传输通道,进程一退,通道就断。
隧道进程启动后会打印出 RSD 的地址和端口,形如127.0.0.1 54321(Windows 上可能是另一段地址)。这个地址端口要传给后续每一条命令,或者用更省事的--tunnel <udid>让它自己去发现。
4.2 起隧道的两条路径
路径一,常驻守护模式,适合长时间调试:
# 需要 root 权限,前台常驻,输出 RSD 地址和端口 sudo ~/.venvs/iosloc/bin/pymobiledevice3 remote tunneld这条命令会一直占着终端不返回,属于正常。它会为每台连接的设备建一条隧道,并打印出类似RSD address: 127.0.0.1, RSD port: 54321的信息,把它抄下来。开第二个终端窗口跑定位命令。
路径二,一次性启动,适合脚本化:
# 起一条隧道并把 RSD 信息输出到标准输出后退出 sudo ~/.venvs/iosloc/bin/pymobiledevice3 remote start-tunnel --udid <你的UDID>这条命令输出完就退出,隧道也随之销毁,只适合「起隧道 → 立刻发一条命令 → 收工」的场景。要连续注入轨迹,还是用守护模式。
Windows 上把sudo换成「以管理员身份运行 PowerShell」,网卡驱动装好之后命令是一样的。如果报「无法创建网络接口」,八成是驱动没装或者装完没重启。
注意:隧道起来之后不要拔线,也不要让设备锁屏休眠。iOS 17 上设备进入深度休眠会断掉隧道,命令表现为超时。调试期间把自动锁屏设成「永不」。
4.3 单点定位与轨迹回放
隧道通了,定位就是一条命令的事。先做单点,确认链路是通的:
# 设置一个固定坐标(纬度在前,经度在后) pymobiledevice3 developer dvt simulate-location \ --rsd 127.0.0.1 54321 \ set --latitude 39.90870 --longitude 116.39750--rsd后面跟隧道打印出的地址和端口,两个参数用空格分开。set子命令设置的是一个静态坐标,设备上所有 App 拿到的定位都会变成这个点。打开地图类 App 看一眼,蓝点跳过去就说明全链路通了。
要还原真实定位,执行clear:
pymobiledevice3 developer dvt simulate-location --rsd 127.0.0.1 54321 clear轨迹回放用 GPX 文件,这是运动类 App 联调最常用的方式:
# 按 GPX 里的时间戳逐点回放,模拟真实移动 pymobiledevice3 developer dvt simulate-location \ --rsd 127.0.0.1 54321 \ play ./run_5k.gpx --distance 1.0play会按 GPX 里每个点的<time>时间戳决定停留时长,逐点推送。--distance参数控制相邻点之间的距离阈值(单位米),小于这个值的点会被合并,避免点太密导致推送压力过大。默认值偏小,长距离轨迹回放时调大一点更稳。
常用的三个子命令参数对比:
| 子命令 | 作用 | 关键参数 | 典型场景 |
|---|---|---|---|
| set | 设置静态坐标 | --latitude / --longitude | 验证链路、测定位漂移 |
| play | 按 GPX 回放 | 文件路径 / --distance | 跑步轨迹联调 |
| clear | 还原真实定位 | 无 | 测完收尾,必做 |
4.4 自己生成一份跑步轨迹 GPX
现成的轨迹文件不一定符合测试需求——配速、总距离、采样密度都要能调。写个小脚本生成,比到处找文件靠谱:
import math import datetime def make_gpx(lat, lon, distance_m, pace_s_per_km, step_s=1.0, out="run.gpx"): """从起点沿正北方向生成一条匀速直线跑步轨迹""" speed = 1000.0 / pace_s_per_km # 配速换算成米/秒 step_m = speed * step_s # 每个采样点前进的距离 n = int(distance_m / step_m) # 总采样点数 m_per_deg_lat = 111320.0 # 纬度 1 度约等于的米数 m_per_deg_lon = 111320.0 * math.cos(math.radians(lat)) # 经度随纬度收缩 t0 = datetime.datetime(2024, 5, 1, 8, 0, 0, tzinfo=datetime.timezone.utc) pts = [] for i in range(n + 1): d = i * step_m cur_lat = lat + (d / m_per_deg_lat) cur_lon = lon cur_t = t0 + datetime.timedelta(seconds=i * step_s) pts.append((cur_lat, cur_lon, cur_t)) with open(out, "w", encoding="utf-8") as f: f.write('<?xml version="1.0" encoding="UTF-8"?>\n') f.write('<gpx version="1.1" creator="localtest" ' 'xmlns="http://www.topografix.com/GPX/1/1">\n<trk><name>run</name><trkseg>\n') for la, lo, t in pts: f.write(f'<trkpt lat="{la:.7f}" lon="{lo:.7f}">' f'<time>{t.strftime("%Y-%m-%dT%H:%M:%SZ")}</time></trkpt>\n') f.write('</trkseg></trk></gpx>\n') # 5 公里、6 分整配速、每秒一个采样点 make_gpx(39.90870, 116.39750, 5000, 360, 1.0, "run_5k.gpx")pace_s_per_km是每公里用多少秒,6 分整配速就是 360。step_s是采样间隔,1 秒一个点对跑步来说足够了,自行车或者车辆测试可以放到 0.2 秒。m_per_deg_lon那行容易被忽略——同样的经度差,在高纬度对应的实际距离更短,不按余弦收缩的话,轨迹会被拉长。
生成出来的是一条正北方向的直线。要拐弯就把方向角加进去,把d分解成南北和东西两个分量分别累加,逻辑不复杂,改两行就行。
提示:GPX 里的时间戳必须是 UTC 且带
Z后缀。写成带时区偏移的格式,部分解析器会直接忽略时间字段,退化成瞬间跳完全程。
5. 踩坑排查:五条花钱买来的经验
这套环境最难受的地方在于报错信息极其吝啬,多数时候只是一句超时。下面五条是我在三台机器、四个系统版本上反复撞出来的,按「现象 → 原因 → 解决」记,遇到对得上的直接抄。
5.1 安装与权限层
现象一:终端提示pymobiledevice3: command not found,但pip show确实装着。
原因是装到了某个 Python 解释器的 site-packages 里,而这个解释器的 bin 目录不在当前 shell 的 PATH 里。pipx 装完不重开终端、venv 里装完但新开窗口没激活,都会这样。解决方式是用which -a pymobiledevice3和python -c "import pymobiledevice3; print(pymobiledevice3.__file__)"对一下路径,确认它们在同一个前缀下;然后用绝对路径调用,或者把对应的 bin 目录写进~/.zshrc/~/.bashrc。
现象二:sudo之后命令找不到,或者报模块导入失败。
sudo默认重置 PATH 和环境变量,venv 里的包自然就丢了。血泪经验是:凡是需要 root 的命令,一律用绝对路径,别指望sudo继承当前环境。写成sudo /home/you/.venvs/iosloc/bin/pymobiledevice3 ...,一次写死,后面省心。如果坚持要继承环境,sudo -E加env_keep也能做到,但那是在给自己埋雷。
现象三:Windows 上设备列表永远是空的。
移动设备支持组件没装,或者装了但服务没起来。查「服务」里那个和设备通信相关的服务是否在运行,没运行就手动启。另外 USB 口也有讲究,前置面板的口和扩展坞转出来的口经常供电不足,换到主板直出的口上重试——这个玄学问题我至少遇到过三次。
5.2 隧道与定位层
现象四:remote tunneld起不来,报无法创建网络接口,或者起来之后所有命令都超时。
分两种原因看。创建失败基本是权限或驱动问题:macOS/Linux 上忘了sudo,Windows 上没装网卡驱动或者没用管理员终端。起来之后超时,多半是设备那边断了——锁屏、拔线、切换了 USB 口。解决办法是先确认设备解锁且自动锁屏已关,再重新插拔,最后重启隧道进程。隧道进程是黑匣子,出问题就杀掉重起,别在里面找原因。
现象五:set返回成功,但地图 App 上的蓝点纹丝不动。
原因有三种,按概率排:一是开发者镜像没挂上,命令走了但设备侧服务根本没起,表现为「成功但无效果」;二是 App 自己缓存了上一次定位,杀掉 App 重开就好;三是之前用过clear之后没有重新set,或者set之后设备重启过,模拟状态已被清掉。排查顺序是:先mounter list确认镜像在,再杀掉 App 重进,最后重新执行一次set。三招用完还不动,就去重启隧道进程。
注意:
clear一定要在测试收尾时执行。忘了执行,设备会一直停在假坐标上,第二天打开导航发现人在几百公里外,那种慌张感一次就够了。
6. 验证跑通:三条链路依次自检,别跳步
装完之后怎么算「真的通了」,这件事得有个客观标准,不然很容易自我安慰。我的做法是三条链路依次过,任何一条不过就停在原地查,不往下走。
第一条,设备链路。pymobiledevice3 usbmux list输出 UDID,pymobiledevice3 lockdownd info输出完整的设备信息,两条都过才算前两层正常。第二条,隧道链路。守护进程打印出 RSD 地址端口,且这个进程能持续存活超过 30 秒不退出——能活过 30 秒说明虚拟网卡真的建起来了,不是诈胡。第三条,定位链路。先set一个离你当前位置足够远的坐标(至少跨省,一眼能看出来),打开系统地图确认蓝点跳过去,再clear确认跳回来。
三条链路都过,再做一次完整的轨迹回放验证:
# 1. 确认镜像已挂载 pymobiledevice3 mounter list # 2. 生成一条 1 公里的短轨迹做快速验证 python make_gpx.py # 3. 回放,观察终端是否有逐点推送的日志 pymobiledevice3 developer dvt simulate-location \ --rsd 127.0.0.1 54321 play ./run_5k.gpx --distance 1.0 # 4. 收尾务必清理 pymobiledevice3 developer dvt simulate-location --rsd 127.0.0.1 54321 clear回放过程中盯两件事:终端有没有持续输出,以及地图上的蓝点是不是在缓慢移动而不是瞬间跳完。瞬间跳完说明 GPX 的时间戳没被正确解析,回去检查<time>的格式。
| 验证项 | 通过标准 | 不通过先查 |
|---|---|---|
| 设备可见 | usbmux list 有 UDID | USB 驱动、线、口 |
| 配对有效 | lockdownd info 有完整字段 | 重置信任关系 |
| 开发者模式 | developer-mode-status 返回 true | 重启 + 设置里手动开 |
| 镜像挂载 | mounter list 有一条已挂载 | 构建号是否匹配 |
| 隧道存活 | 进程持续 30 秒以上 | 权限、网卡驱动 |
| 定位生效 | 地图蓝点跳到目标点 | 镜像、App 缓存 |
| 定位还原 | clear 后蓝点回到真实位置 | 是否重复 set 过 |
还有一个我自己的小技巧:把这一整套命令写成一个run.sh,开头set -e,每步之间sleep 2,最后接一条clear放在trap里。这样中途 Ctrl+C 中断也不会把设备留在假坐标上。后悔药提前备好,比事后慌了神强。
从那以后我每次上真机做轨迹联调,都强制先把这三条链路完整跑一遍再动业务代码,一次不落——因为跳过验证省下的五分钟,通常在半小时后以更难看的方式还回来。希望帮到你。
本文还有配套的精品资源,点击获取