免越狱动态库注入:TrollFools工具链原理与实战指南

1. 先回答一个问题:免越狱动态库注入到底解决什么痛点

最近我身边的开发者群里,免越狱动态库注入这个话题又被频繁翻出来了,核心工具是圈内叫 TrollFools 的一套工具链,我下面统一简称 TF 工具链。很多人第一次听到“免越狱注入”,第一反应是“这怕不是要折腾系统文件”,其实不是,它针对的是一个非常实际的开发痛点:在不修改主程序可执行文件、不依赖越狱环境的前提下,把编译好的动态库加载进某个 iOS 应用的进程里。

为什么要做这件事?说得直白一点,iOS 开发里最消耗耐心的环节,往往不是写代码本身,而是“改一行代码、重新编译、重新签名、重新安装、重新启动验证”这条又臭又长的链路。就拿最普通的 UI 调试举例:我想看看某个页面在改了间距之后长什么样,传统做法是把整个 App 打包签名再跑一遍,光等待构建就要一两分钟,真机安装又要十几秒,如果中途发现还要再调,那更是一眼望不到头。要是碰上做逆向分析、学习别人 App 的交互实现,或者在已有 App 里临时挂一个自动化测试辅助库,传统流程几乎不可用,因为你根本没有源码和重签名的条件。

TF 工具链这种免越狱注入方案,解决的就是这个“快速迭代”和“临时挂载”的问题。它更像是一套“进程外装配线”:把动态库放到指定位置,重启目标 App,库就被系统动态加载器自然地带上,不需要动 App 本身,也不需要给主程序重新签名。整个过程听起来很“魔法”,但背后的机制其实有清晰的路径可循。

如果你属于下面这几类人,这篇内容应该能省掉你不少时间:

  • 频繁调试自定义 UI 或业务逻辑,受够了反复打包签名的
  • 做逆向学习和竞品分析,想在不破坏原 App 的前提下观察行为
  • 开发自动化测试工具,需要往被测 App 里注入测试桩
  • 想给个人开发的小工具挂上快捷键、日志钩子等辅助能力的

需要先说清楚边界:这套方式只适合处理你拥有或明确获得授权的应用,合规使用是底线。下面我从原理、准备、实操、排障四个维度完整过一遍,把我实际踩过的坑也一并交代清楚。

2. 动态库注入的原理拆解:从 dyld 到 TF 工具链的取舍

2.1 一次启动过程,动态库是如何进入内存的

要理解注入,先得理解 iOS 应用启动时发生了什么。很多人印象里 App 启动就是“系统把可执行文件读进来跑 main 函数”,实际上没那么简单。在 main 函数之前,系统会先启动一个叫 dyld 的动态加载器,它的工作是根据可执行文件里记录的动态库依赖列表,把一个个 .dylib 库加载到内存,完成符号绑定,最后才把控制权交给 App 的 main。

这个依赖列表就在 Mach-O 文件格式里,路径由 LC_LOAD_DYLIB 这样的加载命令描述。应用程序里常见的 Foundation、UIKit 都在其中,系统启动时按顺序加载。这里的可执行文件,默认只信任系统库和自身签名时认可的库,第三方库想挤进来必须过签名验证这一关。传统越狱环境下的注入思路有两种:要么直接修改可执行文件,往 LC_LOAD_DYLIB 里加一条;要么通过 DYLD_INSERT_LIBRARIES 这类环境变量在进程启动前临时塞库。前者侵入性强,后者对很多现代系统版本和受管应用已经失效。

一个特别容易让新手困惑的点是:为什么动态库能“注入”但可执行文件不能随便“注入”?因为动态库的加载路径发生在进程启动早期,而 dyld 对库的校验严格依赖签名机制;一旦有权修改某些受信任的可写路径,再配合系统对特定库目录的宽松处理,注入就有了空间。TF 工具链本质上就是把这个空间给稳定地打通了。

2.2 免越狱注入的几条主流思路对比

我把已知的几种注入思路放在一张表里,这样对比更直观:

注入路线是否需要越狱重启后是否保留对主程序侵入性操作门槛
环境变量注入(DYLD_INSERT_LIBRARIES)多数场景需要否低中
直接修改主可执行文件需要是高中
文件系统容器注入否是低低

先说环境变量注入。这是早期很常用的手法,通过给进程设置 DYLD_INSERT_LIBRARIES 环境变量,让 dyld 在启动时额外加载指定库。问题在于,很多受管应用和系统服务在较新的系统上会忽略甚至拦截这个变量,而且进程每次启动都要配置一次环境,无法做到真正的“注入后永不过期”。

再说修改主可执行文件。把 LC_LOAD_DYLIB 命令直接写进 Mach-O 文件里,理论上最直接,但风险也最高:主程序一旦被改动,签名校验必然失败,系统会直接拒绝启动或触发回弹。这也是为什么传统做法必须搭配“取消签名校验”一起用,那基本就等于越狱了。

第三种就是 TF 工具链走的路:利用本体工具所依赖的环境能力,把动态库写入目标 App 容器中一个特定的库目录,再借助系统对容器文件较高的信任度,让 dyld 在加载主程序时顺带加载这个库。整个过程主可执行文件保持原样,也不需要调整签名,重启之后依然生效。它的侵入点集中在文件系统的权限控制层面,所以被归到“免越狱”。

2.3 为什么 TF 工具链能做到“免越狱”:核心设计思路

TF 工具链的核心设计可以概括成一句话:与其篡改“程序本身”,不如打点“程序周围的环境”。iOS 的沙盒机制规定每个 App 有自己的容器目录,容器内有一部分路径允许应用写入,但系统动态加载器对容器内部分特殊位置的库文件采取了更宽松的校验策略。TF 工具通过它自身的分发环境获得对这些容器的管理权限,把动态库“放进”目标 App 受信目录的库路径下,再触发或者等待 App 重启,dyld 就会按照规则把库加载进进程。

这样做的好处很实际:不需要越狱,不修改主程序文件,不破坏原有签名,升级 App 后部分注入状态甚至还能保留(要分情况)。缺点也不是没有:注入库与被注入对象的兼容性得自己保证,库崩了只会导致 App 闪退,系统不会帮你兜底;另外工具本身要求对应的系统环境,这部分会在准备章节里细说。

我在第一次接触这套方案时,确实有过“这东西到底稳不稳”的疑虑,实测下来,只要库本身没问题,加载成功率非常高,重启设备后也依然有效。它不像越狱注入那样动辄影响全局系统,反而是把影响范围圈定在单个容器内,日常使用非常克制。

3. 实操前准备:兼容性、库格式与备份

3.1 设备与系统兼容性怎么判断

动手之前先确认自己的设备在不在支持范围里。TF 工具链依赖特定的安全环境能力,不同 iOS 版本对这种能力的开放程度不一样,较早的系统理论上也能用,但工具作者和维护者通常只保证一部分系统区间内的稳定性。

我自己习惯的判断顺序是:

  • 先看工具说明里标注的支持系统区间,例如是否要求最低版本、最高是否封顶
  • 再确认手里设备是 arm64 架构,老旧的 armv7 设备基本不用考虑
  • 最后看目标 App 本身是否属于可被注入的范围,例如系统自带应用和个别高度受管应用可能会例外

如果你手里的设备不在推荐区间内,也不是完全不能用,但你要有心理准备:可能注入完成后库不加载,也可能 App 直接闪退。它不像编译报错那样有明确提示,排查成本会高不少。所以在准备阶段多花两分钟核对版本,能省掉后面一小时。

3.2 动态库格式与架构要求

注入是用动态库,也就是 .dylib 文件,或者 .framework 形式的动态库框架。很多人一开始拿 .a 静态库来试,那当然不行,因为静态库在编译时已经被链接进可执行文件了,不存在“动态加载”这一步。

库文件本身有几个硬性要求:

检查项要求原因
文件格式Mach-O 动态库iOS 只认 Mach-O,ELF 属于另一套体系
CPU 架构arm64 / arm64e真机 64 位,模拟器架构在真机无效
签名状态可无签名或带调试签名工具链会按目标环境处理
依赖库尽量只依赖系统框架依赖第三方私有库容易加载失败

这里最容易踩的坑是架构不匹配。很多人从网上找来的库是 x86_64 模拟器构建的,放进真机后启动瞬间就会因“坏 CPU 类型”崩溃。我在后期测试阶段吃过一次亏,后来固定做法是每次拿到库先用命令行看一眼架构:lipo -info 库文件路径,看到arm64之后再注入,基本不会出这种低级问题。

3.3 别嫌麻烦:注入前的备份姿势

很多工具介绍都不会强调备份这一步,但我强烈建议做。注入本身不直接改动目标 App 的用户数据,可一旦库有 bug,闪退反复发生,有可能导致 App 内部缓存异常,某些场景下甚至让你误以为数据损坏。免得事后抓瞎,最简单的方式是提前做两层保险。

第一层是数据备份。用系统自带备份功能对设备做一次完整备份,或者用通用文件管理工具单独导出目标 App 的沙盒目录。第二层是状态记录。把当前注入前 App 的版本号、系统版本、注入库的哈希值记录下来,万一后面想排查“是不是这个库有问题”,可以快速回溯。

有一个习惯我建议尽量养成:每次只注入一个库,验证稳定了再加下一个。别一口气把五六个库同时塞进去,出问题的时候你根本分不清是哪个库在捣乱。这是我的血泪教训。

4. 手把手注入一次:完整流程与现场记录

4.1 准备一个动态库的例子

理论讲完,我们走一遍实战。先拿我自己常用的小调试库作为示例,它是一个很简单的 Objective-C 动态库,作用是在加载时打出一条日志,方便确认注入是否成功。

#import <Foundation/Foundation.h> __attribute__((constructor)) static void debug_kit_init(void) { NSLog(@"[DebugKit] injected library loaded at process start"); }

用__attribute__((constructor))标记的函数会在动态库加载完成后的第一时间执行,所以只要看到[DebugKit]开头的日志,就说明库确实进入了进程。编译成动态库也很直接:

gcc -dynamiclib -fobjc-arc -framework Foundation debug_kit.m -o DebugKit.dylib

需要注意,这个编译产物默认是当前编译环境架构。如果是在 macOS 上编译,要额外用-arch arm64指定真机架构,或者编译后用lipo裁剪出 arm64 切片。我用的是最朴素的方式:先编出来,再lipo -thin arm64 -output DebugKit_arm64.dylib DebugKit.dylib。

日志库只是验证链路用的,实际工作中我更常用的是挂载一个 UI 层级查看库,或者一个网络请求日志库,原理完全一样,只是库内部代码更复杂一些。

4.2 通过 TF 工具链执行注入

库准备好之后,把 dylib 文件传到设备上。传文件的方式不限定,能放进 TF 工具链能读取到的位置即可。

操作链路大概是这样:打开 TF 工具链,在主界面上能看到设备上已安装的应用列表;选中目标 App,选择“注入”或“添加”选项,然后从文件列表里挑选刚才传上去的 dylib。确认后,工具会把库复制到目标 App 的容器目录,并在容器内建立对应的加载配置。整个过程通常在几秒内完成,界面上会显示注入列表和状态。

注入完成后,还要做一件事:完全退出目标 App,再重新打开。很多库在进程启动早期加载,如果 App 一直挂在后台没有重启,注入不会生效。我会习惯性地把 App 从后台卡片里划掉,再冷启动一次。这一步看着简单,却是新手最容易漏的环节。

工具界面上一般还提供“移除”和“管理注入列表”的功能。列出当前已经注入的库、删除某个库或清空全部注入状态,都在同一个入口里完成,出现问题时恢复起来很快,动态库的分量比“补丁式”方案轻非常多。

4.3 注入后的验证与回滚

注入完成不等于验证完成。我最常用的验证办法是看系统日志:

log stream --predicate 'processImagePath CONTAINS "你的目标App名"' --style compact

冷启动目标 App,观察日志流里有没有出现[DebugKit] injected library loaded这样的自定义输出。看到了,说明动态库确实被加载执行;看不到,则说明配置、架构或路径某一环有问题,按上一章提到的检查项逐项排查。

另一种验证方式更贴近用户侧:如果注入的是行为增强类库,直接操作 App 界面,观察功能是否出现。比如我做过一个给某内部应用加“长按列表项呼出调试菜单”的库,注入后长按列表项,菜单立刻弹出,这就是最直观的验证。

回滚也简单。常规做法是在 TF 工具链里移除对应库条目,然后冷启动 App,注入效果就消失了。因为主程序自始至终没有被改动,移除库只是让 dyld 不再加载它而已。这比“备份原文件再覆盖回去”的旧式方案安全得多。

4.4 我实测常用的组合方式

几种组合方式用下来比较顺手,也可以当作思路参考:

  • 调试 UI:注入自动顶栏显示当前页面类名的库,走查页面结构很快
  • 日志增强:注入网络层日志库,观察内部请求顺序和参数
  • 自动化辅助:注入定时任务库,让某个测试 App 在固定时间触发特定流程

我目前最常用的是“UI 类名透出 + 网络日志”一套组合,单独验证没问题后再同时启用,没有出现相互干扰。如果你也打算一次注入多个库,建议遵循前面提到的“逐只验证”原则,等库之间交互情况摸清了再合并。

5. 常见问题与排查技巧实录

5.1 注入后 App 闪退

这是最普遍的现象,症状就是冷启动后立刻回到桌面。原因通常集中在三块:

现象最常见原因排查方向
启动几秒内闪退动态库内部执行了非法操作最小化库代码,只保留一个 NSLog 再试
启动瞬间直接闪退架构不匹配或库文件损坏lipo -info检查架构,file检查文件格式
只有特定系统版本闪退库依赖的系统接口在当前版本不可用换用低版本系统接口或用更保守的 API

处理逻辑不复杂,但要有顺序。先把库内容降到最简单,确认不是库逻辑本身的问题;再核对架构和签名;最后才考虑系统和 App 兼容性。绝大多数闪退问题在“最小化复现法”面前都能快速定位。

5.2 库已注入但完全不生效

注入后没有日志输出、没有行为变化,第一反应不应该是“工具坏了”,大概率是加载流程没走到。常见原因是 App 进程根本没重启,或者启动时加载的并非你看到的那个进程实例。还有一个容易被忽略的细节:某些 App 会有多进程模式或者看门狗机制,注入库只在主进程加载,独立子进程不一定受影响。

我排查这类问题时的固定路径是:

  • 确认 App 已完全退出,从后台卡片划掉,而不是只按 Home 键
  • 检查注入列表,确认库路径没有填错目标应用
  • 观察日志流里 dyld 的加载过程,看库是否被尝试加载但失败
  • 查看库文件是否还在容器目录里,有时候系统清理会把它挪走

5.3 注入后 App 启动变慢或卡在主界面

启动变慢通常有两个来源。一是库文件体积过大,加载时磁盘读取耗时明显;二是库内部在 constructor 函数里做了大量初始化,比如访问网络或者大量文件读写,阻塞了启动流程。解决办法是把耗时操作放到后台线程,或者延迟到应用启动完成后再执行。

卡在主界面则更可能是库和 App 主线程产生死锁,常见于库内部调用dispatch_sync到主队列,而主队列此时正在等待其他事件。这种问题没有通用修复公式,只能从库的并发设计入手,原则上注入库要尽量避免阻塞主线程。

5.4 几类我不会拿来注入的场景

最后说说不建议碰的场景。第一是明显违反使用者协议或涉及商业机密窃取的行为,完全没有必要踩;第二是系统自带关键应用,比如设置、电话这类,注入风险极高,一旦出问题恢复成本很大;第三是自己没有授权测试权限的第三方商业应用,技术能力不应该成为逾越边界的借口。

我个人目前用它都是围绕自己参与开发的应用,以及已获授权研究学习的内部项目。工具本身是中性的,但能做出什么价值,取决于使用者把能力用在哪儿。

6. 写在后面:这套工具链的真实体验

用了几个月之后,我最大的感受是:它把“动态库注入”这件事从越狱用户专属的玩具,变成了普通开发者也能快速上手的研究手段。以前要验证一个想法,免不了在打包签名上消耗大量时间,现在的工作流变成了“改库、编译、注入、重启、看日志”,一条链路跑得非常顺。

但从另一面讲,它也不是万能钥匙。限制还在那里:库的兼容性要自己负责,设备系统版本有区间要求,部分受管明显的高安全应用依然注入不进去。正视这些边界,工具反而用得更踏实。

最后分享一个小技巧:我习惯在开发阶段把库的版本号编译进文件内容里,比如每个库都带一个kDebugKitVersion常量,注入后在日志里一起输出。这样当设备上同时存在多个注入库版本时,一眼就能看出当前加载的是哪一版,排查问题时不至于靠猜。这套习惯从一开始培养起来,后面能少走很多弯路。

相关新闻

Vue 2 响应式基石:Object.defineProperty 原理与实战

Vue 2 响应式基石:Object.defineProperty 原理与实战

先说一个几乎人人都会遇到的现象&#xff1a;Vue 2 项目里&#xff0c;data 中声明好的属性随便改&#xff0c;视图同步刷新&#xff1b;但你运行中给某个对象新塞一个此前没有定义过的属性&#xff0c;页面纹丝不动。网上查到的答案是"用 Vue.set"&#xff0c;照着写…

2026/10/21 7:17:54 阅读更多 →
SwarmUI 深度解析:基于 .NET 的开源 AI 图像生成 Web UI 架构与部署实践

SwarmUI 深度解析:基于 .NET 的开源 AI 图像生成 Web UI 架构与部署实践

1. 为什么一个 .NET 开发者会盯上 AI 图像生成的 Web 界面第一次接触 SwarmUI 是在一个本地图像生成的小圈子里&#xff0c;当时群里有人丢了一句"这玩意儿是 .NET 写的&#xff0c;跑起来比想象中轻"&#xff0c;我第一反应是有点意外。因为过去两年&#xff0c;围绕…

2026/10/21 7:17:54 阅读更多 →
车载AI长期记忆架构:从Generative Agents到智能座舱实践

车载AI长期记忆架构:从Generative Agents到智能座舱实践

1. 从一个被忽略的体验断层说起车载语音助手这几年进步很快&#xff0c;唤醒率、识别准确率、指令覆盖度都在肉眼可见地提升。你坐进车里说“我有点冷”&#xff0c;它能调空调&#xff1b;说“导航去公司”&#xff0c;它能规划路线&#xff1b;说“放首周杰伦”&#xff0c;它…

2026/10/21 7:17:54 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年&#xff0c;我大部分时间都在跟浏览器标签页打交道&#xff0c;直到第一次把桌面应用跑起来&#xff0c;才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词&#xff0c;最开始是在某段时间频繁出现在同事的讨论里&#xff0c;一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档&#xff0c;主要面向智能锁安装人员、终端用户及售后维护者&#xff0c;帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件&#xff0c;大小4.37MB&#xff0c;内容结构清晰&#xff…

2026/10/21 9:32:40 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统&#xff0c;整车要求空载跑到8m/s&#xff0c;电机转速已经拉到额定点&#xff0c;驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令&#xff0c;转速纹丝不动&#xff0c;三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年&#xff0c;用它写过嵌入式驱动、做过游戏服务端&#xff0c;也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词&#xff0c;我其实挺感慨的——这门语言的难点从来不是语法多&#xff0c;而是知识点之间的耦合太深…

2026/10/21 9:32:40 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书以PDF格式发布&#xff0c;适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开&#xff0c;先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能&#xff0c;再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历&#xff1a;翻开源码注释、论文摘要或者一份专业报告&#xff0c;每个字都认识&#xff0c;连在一起却像天书。想找人聊聊&#xff0c;身边要么没人懂&#xff0c;要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型&#xff0c;几秒钟之后&a…

2026/10/21 9:31:32 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 16:24:48 阅读更多 →