Python .pyd文件解析:从PE结构到逆向工程的完整指南
1. 项目概述为什么我们需要解析.pyd文件在Python生态里.pyd文件一直是个既熟悉又神秘的存在。很多开发者尤其是刚接触Python与C/C混合编程的朋友都曾遇到过它——当你用pip安装某些高性能库比如numpy,pandas,opencv-python时最终下载到site-packages目录下的除了.py文件往往就有一个或多个.pyd文件。从表面看它就是一个Windows动态链接库DLL但它的内部却封装着可以直接被Python解释器导入和调用的函数与对象。那么什么情况下我们会需要去“解析”一个.pyd文件呢这绝不仅仅是出于好奇。最常见的场景是逆向工程与安全分析。你可能需要审计一个闭源的第三方.pyd模块了解其内部实现了哪些函数、接收什么参数、是否存在潜在的后门或漏洞。另一个场景是故障排查与调试。当一个导入.pyd的Python程序崩溃抛出一个晦涩的内存错误你手头只有这个二进制文件没有源代码如何定位问题再者在软件兼容性与迁移工作中你可能需要确认一个遗留的.pyd模块是否能在新的Python版本或操作系统上运行这就需要解析其依赖和导出符号。简单来说解析.pyd文件就是打开这个黑盒窥探其内部的函数结构、依赖关系和元数据是进行深度开发、调试或安全评估的必备技能。2. .pyd文件的本质与结构解析在深入解析手段之前我们必须彻底理解.pyd到底是什么。很多人误以为它是一种特殊的文件格式其实不然。.pyd本质上就是一个标准的Windows PEPortable Executable格式的DLL文件只不过把扩展名从.dll改成了.pyd。这个重命名是一个约定用于告诉Python解释器“嘿这个DLL是专门为你准备的可以用import语句直接导入。”2.1 PE文件结构简述既然.pyd是DLL它的结构就遵循PE规范。一个典型的PE文件包括.exe和.dll主要包含以下几个部分DOS头与DOS存根为了向后兼容老DOS系统现在已无实际作用。PE文件头包含关键信息如机器类型、区块数量、文件特征是否是可执行文件、是否是DLL等。可选头虽然叫“可选”但对可执行文件至关重要。它包含了程序的入口点、代码段基址、内存中映像大小、子系统类型控制台/GUI、以及数据目录表。数据目录表指向其他重要结构如导入表、导出表、资源表等这是我们解析的重点。节区表描述文件中各个节区Section的属性如代码节.text、数据节.data、只读数据节.rdata、导入/导出信息所在的节通常是.rdata或.edata等。节区数据实际的代码、数据等内容。对于.pyd文件我们最关心的是导出表。一个DLL.pyd通过导出表向外界宣告“我这里提供了这些函数你们可以来调用。”Python的ctypes模块或import机制正是通过查找这个导出表来定位并加载模块初始化函数的。2.2 Python模块的初始化约定一个合格的.pyd文件必须导出一个特定的函数作为模块的入口点。这个函数的名称取决于Python的版本和模块名对于Python 3约定是PyInit_模块名其中模块名是去掉.pyd后缀的文件名。例如模块spam.pyd必须导出函数PyInit_spam。对于Python 2约定是init模块名。当执行import spam时Python解释器会加载spam.pyd在其导出表中寻找PyInit_spam函数然后调用它。这个函数负责创建并返回一个模块对象完成模块的初始化。因此解析.pyd文件首要任务就是确认这个初始化函数是否存在并查看它还导出了哪些其他可能供内部使用的函数。3. 核心解析工具与方法实战解析.pyd文件不需要自己从零解析PE格式我们可以借助一系列成熟的工具链。下面我将从简单到复杂介绍几种核心方法。3.1 使用dumpbin工具Windows原生dumpbin是微软Visual Studio自带的一个命令行工具用于查看PE文件信息。它是最直接、最原始的工具。基础使用查看导出函数这是最常用、最快捷的命令能立即看到模块的入口点。dumpbin /exports your_module.pyd执行后你会看到一个列表其中PyInit_your_module或Python 2的inityour_module应该位列其中。如果这个函数不存在那么这个.pyd文件就无法被Python正常导入。深入查看依赖项.pyd文件可能依赖其他DLL例如Python本身的python3x.dll、C运行时库vcruntime140.dll等。查看依赖可以帮助解决运行时“找不到指定模块”的错误。dumpbin /dependents your_module.pyd全面分析查看所有头信息这个命令会输出非常详细的PE文件结构信息包括所有头信息和节区信息适合深度分析。dumpbin /headers your_module.pyd实操心得dumpbin的输出信息量大且原始对于新手可能不太友好。它的路径通常需要添加到系统环境变量或者你需要在Visual Studio的开发人员命令提示符下运行。一个快速定位其路径的方法是在开始菜单搜索“Developer Command Prompt”并打开即可直接使用。3.2 使用Python的pefile库进行编程化解析对于需要在Python脚本中自动化分析.pyd文件的情况pefile库是绝佳选择。它用纯Python实现了PE文件的解析器功能强大且灵活。安装pip install pefile基础解析脚本示例import pefile def analyze_pyd(pyd_path): try: pe pefile.PE(pyd_path) print(f[*] 文件: {pyd_path}) print(f[*] 机器类型: {pe.FILE_HEADER.Machine} (0x{pe.FILE_HEADER.Machine:04X})) print(f[*] 特征: {pe.FILE_HEADER.Characteristics}) print(f[*] 是否DLL: {pe.is_dll()}) # 检查导出表 if hasattr(pe, DIRECTORY_ENTRY_EXPORT): exports pe.DIRECTORY_ENTRY_EXPORT print(f\n[*] 导出函数表 (共{exports.symbols.count}个):) print(f - 模块名: {exports.name.decode() if exports.name else N/A}) for exp in exports.symbols: func_name exp.name.decode() if exp.name else fOrdinal_{exp.ordinal} print(f - {func_name} (RVA: 0x{exp.address:08X})) else: print(\n[!] 此文件没有导出表不是一个有效的DLL/.pyd模块。) # 检查导入表依赖 print(f\n[*] 导入的DLL:) if hasattr(pe, DIRECTORY_ENTRY_IMPORT): for entry in pe.DIRECTORY_ENTRY_IMPORT: dll_name entry.dll.decode() print(f - {dll_name}) # for imp in entry.imports: # print(f - {imp.name.decode() if imp.name else Ordinal_str(imp.ordinal)}) else: print( - 无导入表) pe.close() except pefile.PEFormatError as e: print(f[!] 文件格式错误: {e}) except Exception as e: print(f[!] 解析过程中发生错误: {e}) if __name__ __main__: analyze_pyd(your_module.pyd)脚本功能解读加载与基础信息pefile.PE()加载文件并可以轻松获取机器类型如0x8664代表x64、文件特征等。导出表分析检查DIRECTORY_ENTRY_EXPORT属性是否存在。如果存在遍历symbols即可获得所有导出函数的名字和相对虚拟地址RVA。这是判断.pyd有效性的关键。导入表分析检查DIRECTORY_ENTRY_IMPORT可以列出该.pyd文件运行时依赖的所有其他DLL。这对于排查环境问题至关重要。注意事项pefile在解析某些被混淆或加壳的PE文件时可能会抛出异常。此外导出/导入表中的名称字段可能是None这时函数是通过序号Ordinal导出的在输出时需要做判断如示例代码所示。3.3 使用Dependency Walker或Process Explorer进行可视化分析对于喜欢图形化界面的用户老牌工具Dependency WalkerDepends非常经典。它用树状图清晰地展示了模块的所有依赖关系并高亮显示缺失或错误的DLL。你可以直接打开.pyd文件它会自动递归加载所有依赖项一眼就能看出环境配置的问题所在。另一个强大的工具是Process ExplorerSysinternals Suite的一部分。它更侧重于运行时分析。你可以在Python解释器进程python.exe中找到已加载的.pyd模块右键查看其属性在“映像”或“字符串”标签页里有时能发现一些路径、版本信息等有用的字符串。3.4 使用反汇编工具进行深度代码分析当基础解析无法满足需求需要了解函数内部实现逻辑时就需要反汇编工具了。IDA Pro和Ghidra是行业标杆。IDA Pro交互式反汇编器功能极其强大支持多种处理器架构能生成伪代码F5键极大提升分析效率。对于.pyd你可以用IDA打开它会自动识别为PE文件并解析出所有函数。你可以重点分析PyInit_函数看它是如何构建模块对象的。Ghidra美国国家安全局NSA开源的反汇编工具同样功能强大且免费。它也有优秀的反编译功能。使用这些工具的门槛较高需要具备一定的汇编语言和逆向工程知识。但对于安全研究员或需要深度理解复杂二进制模块的开发者这是必不可少的技能。4. 解析实战从一个具体案例出发假设我们拿到了一个名为cryptic.pyd的第三方模块文档缺失我们想搞清楚它能做什么。第一步快速健康检查dumpbin /exports cryptic.pyd输出显示它导出了一个函数PyInit_cryptic。很好这至少是一个有效的Python扩展模块。第二步编程化深度解析我们编写一个更详细的pefile脚本不仅看导出还尝试提取一些有用的字符串可能包含调试信息、作者、版本等。import pefile import re def deep_analyze(pyd_path): pe pefile.PE(pyd_path) # 1. 导出函数 print( 导出函数 ) if hasattr(pe, DIRECTORY_ENTRY_EXPORT): for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols: name exp.name.decode(utf-8, errorsignore) if exp.name else f[Ordinal_{exp.ordinal}] print(f {name}) # 2. 导入的DLL和函数看它调用了哪些关键API print(\n 关键依赖函数 ) if hasattr(pe, DIRECTORY_ENTRY_IMPORT): api_of_interest [malloc, free, strcpy, sprintf, fopen, system, WinExec] # 关注一些敏感API for entry in pe.DIRECTORY_ENTRY_IMPORT: dll entry.dll.decode(utf-8, errorsignore).lower() # 只关注来自特定DLL的敏感API if kernel32 in dll or msvcrt in dll or advapi32 in dll: for imp in entry.imports: if imp.name: api_name imp.name.decode(utf-8, errorsignore) if api_name in api_of_interest: print(f {dll}!{api_name}) # 3. 扫描字符串节.rdata, .data等 print(\n 可读字符串片段 ) string_pattern re.compile(b[\x20-\x7e]{4,}) # 匹配至少4个连续的可打印ASCII字符 for section in pe.sections: try: data section.get_data() strings string_pattern.findall(data) for s in strings[:20]: # 只打印前20个避免刷屏 try: decoded s.decode(utf-8, errorsignore) # 过滤掉一些无意义的纯符号或短数字串 if len(decoded.strip()) 3 and not re.match(r^[\d\W]$, decoded): print(f [{section.Name.decode().strip()}] {decoded}) except: pass except: pass pe.close() deep_analyze(cryptic.pyd)这个脚本能帮助我们确认模块入口。发现它是否调用了危险函数如system,WinExec这可能是一个安全风险点。从二进制文件中提取出可读的字符串这些字符串里可能隐藏了调试日志、错误信息、硬编码的密钥或URL是理解模块功能的宝贵线索。第三步动态分析静态分析之后可以尝试在受控环境如沙箱、虚拟机中动态加载这个模块。import sys import traceback # 将.pyd文件所在目录加入路径或直接使用绝对路径 sys.path.insert(0, rC:\path\to\pyd\dir) try: import cryptic print(f[*] 模块导入成功: {cryptic}) # 尝试查看模块的属性 print(f[*] 模块属性: {dir(cryptic)}) # 如果有文档字符串 if cryptic.__doc__: print(f[*] 模块文档: {cryptic.__doc__[:500]}...) # 截断显示 except Exception as e: print(f[!] 导入失败: {e}) traceback.print_exc()动态导入可以验证模块功能并通过dir()查看它暴露了哪些属性函数、类、变量。这是最直接的“黑盒测试”。5. 常见问题与排查技巧实录在解析和使用.pyd文件的过程中你会遇到各种坑。下面是我总结的一些典型问题及解决方法。5.1 导入错误ImportError: DLL load failed这是最常见的问题其根本原因是依赖的DLL找不到或版本不匹配。排查步骤使用dumpbin /dependents或pefile脚本首先确认这个.pyd依赖哪些DLL。重点关注python3xx.dll确保Python版本匹配如python39.dll对应Python 3.9。vcruntime140.dll,msvcp140.dll这是Visual C 2015-2022的可再发行组件。通常需要安装对应的Microsoft Visual C Redistributable。其他第三方DLL如libcrypto-1_1-x64.dllOpenSSL等。检查DLL搜索路径Windows按以下顺序搜索DLL应用程序所在目录、系统目录、PATH环境变量。最稳妥的方法是将所有缺失的DLL复制到与.pyd文件相同的目录下。检查架构匹配确保.pyd的架构x86/x64与你的Python解释器架构一致。用dumpbin /headers查看“机器类型”0x14c是x860x8664是x64。在Python中运行import struct; print(struct.calcsize(P)*8)可以查看解释器位数。使用Process Monitor工具如果以上步骤无效使用微软的Process Monitor过滤python.exe进程和“路径”包含“.dll”的操作可以清晰地看到Python解释器在哪些路径下尝试加载DLL并失败了。5.2 导出表中找不到PyInit_模块名可能原因文件损坏文件下载不完整或被破坏。不是有效的Python扩展它可能只是一个普通的DLL并非为Python设计。模块名不匹配.pyd文件可能被重命名过。例如文件叫fast.pyd但内部导出的初始化函数是PyInit_fastcalc。这时你需要用dumpbin /exports看到的实际函数名来导入import fast as fastcalc可能不行更可能需要通过ctypes手动加载。加壳或混淆文件被商业加壳工具保护导出表被隐藏或扭曲。需要先脱壳才能正常解析。5.3 版本兼容性问题一个为Python 3.7编译的.pyd文件很可能无法在Python 3.11上运行因为Python的C API应用程序接口在不同小版本间可能有变动。错误信息可能包含“undefined symbol: PyExc_OSError”之类的字样。解决方案重新编译获取源代码用当前环境的Python和编译器重新编译这是最根本的方法。寻找对应版本的预编译包通过pip安装时指定版本或到非官方仓库如Christoph Gohlke的网站寻找对应版本的wheel.whl文件其中包含了匹配的.pyd。5.4 反编译与逆向工程中的难点当你用IDA或Ghidra打开一个复杂的.pyd时可能会面临符号信息缺失发布版本通常剥离了调试符号所有函数名都是sub_xxxxxxx变量名也无从得知。C代码混淆如果模块是用C编写的函数名会被编译器进行名称修饰Name Mangling变成像?PyInit_ModuleYAPEAU_objectXZ这样的乱码。Ghidra和现代版本的IDA通常能较好地解析这些修饰名还原出可读的函数签名。动态行为分析静态分析只能看代码无法知道运行时数据。需要结合动态调试如使用x64dbg, WinDbg附加到Python进程来观察函数调用时的参数和内存状态。独家避坑技巧对于重要的第三方.pyd模块在项目初期就将其依赖的所有DLL包括Python本身的DLL、VC运行时与.pyd文件一起打包到你的应用程序分发目录中并确保你的应用程序启动时将此目录添加到os.add_dll_directory()Python 3.8或优先搜索路径。这能从根本上解决“在我机器上好好的客户机器上就报错”的经典问题。解析.pyd文件从简单的函数列表查看到深度的逆向工程是一个循序渐进的过程。掌握这些工具和方法不仅能帮你解决日常开发中的依赖和兼容性问题更能为你打开一扇通往二进制安全、性能优化和底层交互的大门。下次再遇到那个神秘的.pyd文件时希望你能自信地拿起工具揭开它的面纱。

相关新闻

Agency-Agents:基于角色协作的AI智能体框架,重塑软件开发流程

Agency-Agents:基于角色协作的AI智能体框架,重塑软件开发流程

1. 项目概述:从“工具人”到“专业团队”的进化如果你和我一样,每天都在和代码打交道,那你肯定用过各种AI编程助手。从最初的代码补全,到后来的对话式编程,它们确实帮我们省了不少敲键盘的功夫。但不知道你有没有这种感…

2026/10/18 13:55:39 阅读更多 →
ncmdump 完整指南:如何快速解密网易云音乐NCM文件并转成MP3/FLAC

ncmdump 完整指南:如何快速解密网易云音乐NCM文件并转成MP3/FLAC

ncmdump 完整指南:如何快速解密网易云音乐NCM文件并转成MP3/FLAC 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 在网易云音乐下载的歌曲,很多都会被保存成 NCM 这种加密格式,换到车载音响、老式播…

2026/10/18 13:55:44 阅读更多 →
gerbv:免费开源Gerber文件查看器如何用一条命令守住PCB制造的最后防线

gerbv:免费开源Gerber文件查看器如何用一条命令守住PCB制造的最后防线

gerbv:免费开源Gerber文件查看器如何用一条命令守住PCB制造的最后防线 【免费下载链接】gerbv Maintained fork of gerbv, carrying mostly bugfixes 项目地址: https://gitcode.com/gh_mirrors/ge/gerbv 在PCB设计交付前,你通常花多少钱和时间在…

2026/10/20 2:03:20 阅读更多 →

最新新闻

基于垂向加速度与MLP神经网络的路面摩擦系数估计

基于垂向加速度与MLP神经网络的路面摩擦系数估计

最近在做一个车辆底盘控制相关的项目,核心目标之一是实时估计路面附着上限,也就是轮胎和路面之间的最大摩擦系数(Tire-Road Friction Coefficient,简称TRFC)。这东西在ABS、ESP、自动紧急制动里都是关键变量&#xff0…

2026/10/20 2:56:47 阅读更多 →
基于C#的多品牌PLC统一监控与智能报警系统设计实践

基于C#的多品牌PLC统一监控与智能报警系统设计实践

第一次走进某工厂的机柜间,我看到的不是一排整齐划一的控制器,而是三套互不联通的监控系统。产线A用的是西门子的控制器,产线B是三菱的,配电间还挂着一台只负责读电表的组态软件。设备工程师处理故障时,要挨个切换窗口…

2026/10/20 2:56:47 阅读更多 →
ok-ww 完整指南:鸣潮后台自动战斗、一键日常与声骸刷取的配置避坑手册

ok-ww 完整指南:鸣潮后台自动战斗、一键日常与声骸刷取的配置避坑手册

ok-ww 完整指南:鸣潮后台自动战斗、一键日常与声骸刷取的配置避坑手册 【免费下载链接】ok-wuthering-waves 鸣潮 后台自动战斗 自动刷声骸 一键日常 Automation for Wuthering Waves 项目地址: https://gitcode.com/GitHub_Trending/ok/ok-wuthering-waves …

2026/10/20 2:56:47 阅读更多 →
MCP协议实战:从原理到构建AI工具调用Server的全指南

MCP协议实战:从原理到构建AI工具调用Server的全指南

这篇笔记是我在连续研究了大半个月MCP(Model Context Protocol,模型上下文协议)之后整理出来的。说实话,第一次看到这个缩写我还以为是某个消息通信协议的缩写,深入进去才发现,这根本就是大模型应用开发的一…

2026/10/20 2:56:47 阅读更多 →
科技商业史复盘:社交收购、5G芯片发布与PC并购的底层逻辑

科技商业史复盘:社交收购、5G芯片发布与PC并购的底层逻辑

时间是一个很奇妙的东西。翻看科技行业的商业史,你会发现很多标志性事件的节点往往挤在同一天。9月6日这一天,就曾经同时落下过三颗足以改变行业走向的棋子:一起改变社交生态的超级收购、一次重新定义移动通信能力的旗舰芯片发布、一桩重塑计…

2026/10/20 2:56:47 阅读更多 →
TensorRT实战手册:GPU推理优化与INT8量化完整指南

TensorRT实战手册:GPU推理优化与INT8量化完整指南

要说GPU推理优化,TensorRT是绕不过去的那一个。框架跑训练、TensorRT做上线推理,这个分工在工业级落地里已经成了默认组合。简单讲,TensorRT是一个专攻推理阶段的高性能优化引擎,它把训练好的模型拿到手,通过层融合、精…

2026/10/20 2:55:46 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →