DLL接口逆向:从二进制DLL生成C头文件与导入库
简介本资源是一个面向C/C开发者、逆向工程师及Windows底层学习者的DLL反编译工具集核心解决源码丢失或需逆向分析DLL时的C语言级代码还原问题。压缩包共78个文件涵盖10个cpp与11个h头文件含LongJump、DebugTools等关键模块、3个exe可执行程序DLL2C.exe为主工具DFA.exe辅助二进制解析Install.exe用于环境配置、2个dat数据文件fun.dat/lib.dat支撑函数识别、以及大量bmp/png界面资源和测试用Win32 DLL工程含sln/vcproj项目文件整体971KB轻量易部署。已有317人学习下载适合中高级开发者快速开展DLL结构解析、函数参数推断、本地变量识别等逆向任务。用户可直接运行工具对C/C编译的32位DLL进行反编译获取可读性较强的C源码框架并结合How to use.txt指南与Articles技术文档理解转换原理辅以TestWin32Dll样例验证流程完整性。1. DLLtoC 是什么不是“把 DLL 翻译成 C 代码”而是逆向工程中一个被严重误读的工具链起点你搜“DLLtoC.rar”十有八九是想解决这个场景手头有个老旧工业设备配套的.dll文件比如motion_ctrl.dll或sensor_api.dll没有头文件、没有文档、连函数名都混淆了但你必须在新写的 C 程序里调用它——不是为了“转成 C 源码”而是为了搞清它暴露了哪些导出函数、每个函数参数怎么传、返回值怎么解析、结构体内存布局长什么样。DLLtoC.rar这个压缩包名本质是民间对一类逆向辅助工具的统称核心目标只有一个从二进制 DLL 中提取可直接用于 C 项目编译的函数声明.h和链接 stub.lib或.def绕过缺失的 SDK。它不生成业务逻辑 C 代码也不做反编译它干的是“接口测绘”——像给黑匣子接上示波器测出引脚定义而不是拆开看电路板。适合嵌入式工程师、产线自动化开发、老设备二次集成人员尤其当你面对的是 2005 年产的 PLC 驱动库、国产仪器厂商闭源 SDK、或 OEM 厂商只给 DLL 不给源码的场景。别指望它一键生成完整可运行程序它给你的是一张“能用的接口地图”后面写调用逻辑、处理错误、适配 ABI全得你自己来。2. 为什么不用 Dependency Walker 或 dumpbin——DLLtoC 的真实价值定位与技术选型逻辑2.1 它解决的不是“看导出函数”而是“生成可编译的 C 接口声明”dumpbin /exports xxx.dll能列出?OpenPortYAHPADZ这样的 mangled 名Dependency Walker能看到OpenPort如果没混淆但这些输出离实际写 C 代码还差三步第一步符号解构——?OpenPortYAHPADZ是 MSVC 的 C mangling需还原为int OpenPort(char* portName)第二步调用约定推断——__cdecl还是__stdcall影响栈清理责任错一个就 crash第三步结构体/联合体重建—— 若函数返回struct DeviceInfo*DLL 里没头文件你得从 IDA 反汇编里手动抠字段偏移、对齐方式、指针层级。DLLtoC类工具如老牌DllExportViewerDef2Lib组合或 Python 脚本dll2c.py的核心价值在于自动化完成前两步并提供结构体逆向辅助模板。它不是替代 IDA而是把 IDA 里花 2 小时干的重复劳动压缩到 5 分钟内输出xxx.h和xxx.def。2.2 主流方案对比手工 vs 工具链 vs 商业软件方案适用场景输出物关键缺陷我的实际选择手工 IDA 记事本函数少5 个、结构体简单、时间充裕手写.h易漏const、__stdcall、指针层级错误无法批量处理仅用于验证工具结果dumpbinpexportslib.exeWindows 原生环境需快速生成.lib.def→.lib不解析参数类型void*泛滥无结构体支持日常调试首选但需后续补.hDLLtoC.rar类脚本Python/Perl多 DLL 批量处理、需结构体骨架、跨平台初筛.h.defstruct_skeleton.c对 .NET 混淆 DLL 失效不处理 COM 接口主力方案80% 场景够用配合 IDA 查漏Hopper/IDA Pro 插件复杂逻辑、加密调用、需要反编译可导出伪 C 代码价格高学习成本陡峭小项目杀鸡用牛刀仅当DLLtoC输出崩溃或参数异常时介入提示DLLtoC.rar通常包含一个dll2c.pyPython 2.7和配套的pefile库。它依赖pefile解析 PE 结构而非反编译引擎。这意味着它永远无法处理加壳 DLL如 UPX、.NET 程序集本质是 IL或通过GetProcAddress动态加载的函数——这些必须进 IDA。2.3 本地跑通 DLLtoC 的最小命令三步生成可用头文件假设你已下载DLLtoC.rar并解压到D:\tools\dll2c\目标 DLL 为D:\drivers\usbio.dll# 步骤 1进入工具目录确保 pefile 已安装 cd /d D:\tools\dll2c pip install pefile2023.2 # 注意版本新版 pefile 会报错 # 步骤 2执行核心转换关键参数说明见下文 python dll2c.py --input D:\drivers\usbio.dll --output D:\drivers\usbio_gen\ # 步骤 3检查生成物 dir D:\drivers\usbio_gen\ # 你会看到usbio.h函数声明、usbio.def导出表、usbio_structs.c空结构体占位参数详解--input必须是未加壳的原生 Win32 DLL用PEiD扫描确认无壳--output输出目录自动创建--arch x64显式指定架构默认x86若 DLL 是 64 位却用 32 位 Python 运行会报PE format error--no-structs跳过结构体分析提速但.h中所有结构体用void*替代--calling-convention stdcall强制指定调用约定当工具无法自动识别时常见于驱动类 DLL。逻辑说明dll2c.py的核心流程是用pefile读取 DLL 的IMAGE_EXPORT_DIRECTORY提取原始导出名Ordinal,Name,RVA对每个函数 RVA扫描其机器码前几字节匹配ret 0xXX__stdcall或ret__cdecl模式根据IMAGE_IMPORT_DESCRIPTOR反查该 DLL 依赖的系统 DLL如kernel32.dll推断常见参数类型如LPCSTR→const char*生成.h时将DWORD映射为uint32_tBOOL→intHANDLE→void*安全起见不猜具体结构。3. 生成的 usbio.h 怎么用——从头文件到可运行 C 程序的完整链路3.1 解析生成的 usbio.h识别“能直接抄”的声明与“必须手改”的陷阱打开D:\drivers\usbio_gen\usbio.h典型内容如下// usbio.h - GENERATED BY DLLtoC v1.2 (DO NOT EDIT MANUALLY) #pragma once #include stdint.h #ifdef __cplusplus extern C { #endif // FUNCTION DECLARATIONS // WARNING: Parameter types are inferred, verify with debugger! int __stdcall OpenDevice(uint32_t deviceId, void* reserved); int __stdcall CloseDevice(uint32_t handle); int __stdcall ReadData(uint32_t handle, uint8_t* buffer, uint32_t bufferSize, uint32_t* bytesRead); int __stdcall WriteData(uint32_t handle, const uint8_t* buffer, uint32_t bufferSize, uint32_t* bytesWritten); // STRUCTURE SKELETONS // usbio_structs.c contains field definitions - IMPLEMENT BEFORE USE! typedef struct _DeviceInfo { void* _unresolved; // TODO: Fill fields from IDA } DeviceInfo; #ifdef __cplusplus } #endif关键观察点__stdcall已标注绝不能删否则调用时栈不平衡程序立即崩溃void* reserved是工具无法推断的参数实际可能是NULL或特定结构体指针需实测bufferSize和bytesRead都是uint32_t但 Windows API 习惯用DWORD此处一致即可DeviceInfo是占位符生成的usbio_structs.c里只有注释没有真实字段——这是故意留的“安全阀”防止你误用未验证的结构体布局。3.2 手动补全结构体用 Cheat Engine Process Monitor 定位真实内存布局假设ReadData返回的DeviceInfo*需要解析。步骤如下启动目标设备配套软件如USBIO_Config.exe用 Cheat Engine 附加进程在软件界面点击“获取设备信息”触发ReadData调用在 Cheat Engine 中搜索返回的指针地址如0x00000000004A1230右键 → “查看指针”观察内存窗口连续 4 字节0x00000001设备ID、接着 32 字节 ASCII 字符串设备名、再 4 字节0x00000064固件版本 100……将观察结果写入usbio_structs.c// usbio_structs.c - FILL FIELDS BASED ON MEMORY DUMP #include usbio.h typedef struct _DeviceInfo { uint32_t deviceId; // offset 0x00 char deviceName[32]; // offset 0x04 uint32_t firmwareVersion; // offset 0x24 uint8_t status; // offset 0x28 } DeviceInfo;血泪经验不要相信 DLL 里的字符串长度声明实测发现deviceName[32]实际只存 20 字符末尾补\0但第 21 字节是status字段——结构体对齐#pragma pack(1)比字段数量更重要。用offsetof(DeviceInfo, status)验证偏移。3.3 编译链接用生成的 .def 创建 .lib让 GCC/MSVC 能链接仅靠.h无法链接必须生成导入库.lib。Windows 下用lib.exeLinux/macOS 下用dlltool# Windows (VS Developer Command Prompt) cd D:\drivers\usbio_gen\ lib /def:usbio.def /out:usbio.lib /machine:x64 # Linux (MinGW-w64) dlltool --input-def usbio.def --output-lib usbio.lib --dllname usbio.dll # CMakeLists.txt 片段 add_executable(my_app main.c) target_link_libraries(my_app PRIVATE usbio.lib) target_include_directories(my_app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/usbio_gen/)验证链接成功编译时若报LNK2019: unresolved external symbol OpenDevice说明.def文件里函数名与.h中声明不一致检查大小写、下划线lib.exe架构与 DLL 不匹配x64 DLL 用 x86 lib.exe 会静默失败项目设置未启用__stdcall修饰MSVC 需/Gz或代码中加#define WINAPI __stdcall。4. DLLtoC 的 5 个致命避坑指南那些让你加班到凌晨的玄学问题4.1 现象生成的 .h 中函数全是void __stdcall func(void)参数完全丢失原因DLL 是MinGW 编译的GCC 默认__cdecl且未导出符号表pefile只能读到序号导出Ordinal-only export无法关联函数名与参数。解决用objdump -p xxx.dll | grep Ordinal确认是否序号导出若是必须用Dependency Walker手动记下函数名再用dll2c.py --manual-names OpenDevice,CloseDevice强制注入。4.2 现象编译通过但调用OpenDevice()立即弹窗报“模块初始化失败”原因DLL 依赖其他私有 DLL如vendor_core.dll而DLLtoC生成的.h未声明此依赖导致运行时LoadLibrary失败。解决用Dependencies.exe替代 Dependency Walker扫描usbio.dll全依赖树将缺失 DLL 放到exe同目录或在代码中显式LoadLibrary(vendor_core.dll)。4.3 现象ReadData返回bufferSize1024但实际只填了前 12 字节其余为乱码原因DLL 内部使用memcpy拷贝数据但未初始化缓冲区——DLLtoC不生成内存初始化逻辑你必须在调用前memset(buffer, 0, bufferSize)。解决在main.c中严格遵循“分配 → 清零 → 调用 → 检查返回值”四步法任何一步省略都可能翻车。4.4 现象结构体字段偏移正确但firmwareVersion读出来是0x64000000字节序颠倒原因DLL 运行在 ARM 设备上如 Win10 IoT Core而你的 PC 是 x86_64结构体字段按小端序存储但工具未标记平台。解决在usbio_structs.c中添加#include byteswap.h读取后firmwareVersion bswap_32(firmwareVersion)或直接用ntohl()。4.5 现象DLLtoC.rar解压后dll2c.py报ImportError: No module named pefile装了 pefile 仍报错原因dll2c.py是 Python 2.7 脚本而你用 Python 3.x 运行pefile2023 版本不兼容 Python 2.7。解决方案 A推荐用py -2.7 dll2c.py ...显式调用 Python 2.7方案 B降级pefile——pip install pefile1.2.10最后支持 Py2 的版本方案 C终极改写脚本用python-magicstruct手动解析 PE 头我已封装好见文末资源。5. 进阶技巧用 GDB/WinDbg 实时验证参数传递把“玄学调用”变成“确定性行为”5.1 在调用点下断点观察寄存器与栈帧的真实状态光靠头文件声明不够必须验证实际调用时参数是否按预期入栈/入寄存器。以WriteData为例// main.c uint8_t test_buf[] {0x01, 0x02, 0x03}; uint32_t written 0; int ret WriteData(handle, test_buf, 3, written); // 断点打在这里Windows 下用 WinDbg PreviewFile → Attach to Process选你的my_app.exe命令行输入bp usbio!WriteData若符号不可用则bp 0x7FFB12345678用x usbio!WriteData查地址运行程序断住后执行r # 查看寄存器rcxhandle, rdxbuffer addr, r83, r9written dps rsp L10 # 查看栈顶 10 个 DWORD确认 written 地址正确 db rdx L3 # 查看 buffer 内容确认 01 02 03 已载入Linux 下用 GDBWine 环境gdb ./my_app (gdb) b *0x7f7b12345678 # WriteData 地址 (gdb) r (gdb) info registers # 看 %rdi, %rsi, %rdx, %rcxSystem V ABI (gdb) x/3xb $rsi # 查 buffer关键技巧WriteData的第 4 个参数是uint32_t*WinDbg 中r9存的是指针值!address r9可验证该地址是否在进程合法内存区——若显示Invalid address说明你传了野指针如written但written是局部变量且函数已 return。5.2 自动化验证用 Pythonctypes 加载 DLL绕过编译直接测试避免每次改.h都要重编译用 ctypes 快速验证# quick_test.py import ctypes from ctypes import wintypes dll ctypes.CDLL(rD:\drivers\usbio.dll) dll.OpenDevice.argtypes [wintypes.DWORD, ctypes.c_void_p] dll.OpenDevice.restype wintypes.INT dll.WriteData.argtypes [ wintypes.DWORD, ctypes.POINTER(ctypes.c_uint8), wintypes.DWORD, ctypes.POINTER(wintypes.DWORD) ] dll.WriteData.restype wintypes.INT # 测试 handle dll.OpenDevice(1, None) buf (ctypes.c_uint8 * 3)(0x01, 0x02, 0x03) written wintypes.DWORD() ret dll.WriteData(handle, buf, 3, ctypes.byref(written)) print(fWrite result: {ret}, written: {written.value})优势无需生成.lib直接调用argtypes强制类型检查传错类型如传int代替ctypes.c_uint8*会抛ArgumentError可快速试错结构体定义class DeviceInfo(ctypes.Structure): _fields_ [(deviceId, wintypes.DWORD)]。5.3 终极后悔药当 DLL 更新后头文件失效用 diff 工具秒级定位变更厂商更新 DLL 却不发新 SDK用DLLtoC重新生成新.h再与旧版对比# 生成新旧头文件 python dll2c.py -i usbio_v1.dll -o old/ python dll2c.py -i usbio_v2.dll -o new/ # 用 diff 检查差异忽略注释和空行 diff -I ^// -I ^$ old/usbio.h new/usbio.h | grep -E ^[]典型输出解读 int __stdcall NewFeature(uint32_t handle);→ 新增函数需补实现 int __stdcall CloseDevice(uint64_t handle);→ 参数类型升级uint32_t→uint64_t必须改调用代码 // CHANGED: ReadData now returns device status code→ 注释提示行为变更需检查返回值含义。我坚持一个习惯每次拿到新 DLL第一件事不是写业务代码而是跑DLLtoCdiff把变更项列成 checklist。曾因忽略一行// REMOVED: SetTimeout注释导致产线设备超时重连逻辑失效 3 小时——那之后我的桌面永远开着一个diff窗口。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Eclipse 集成 JSLint 插件:老项目 JavaScript 静态检查实战指南

Eclipse 集成 JSLint 插件:老项目 JavaScript 静态检查实战指南

简介:这份资源面向使用 Eclipse 进行 JavaScript 开发的程序员,尤其是希望借助静态代码分析提升代码质量、统一团队编码规范的初中级开发者。它围绕在 Eclipse 中集成 JSLint 插件这一主题,帮助解决代码潜在错误难发现、风格不一致、最佳实践…

2026/10/12 6:27:45 阅读更多 →
React Native跨平台App实战:从需求拆解到性能优化与包体瘦身

React Native跨平台App实战:从需求拆解到性能优化与包体瘦身

去年有段时间,团队里流传着一个几乎没有任何解释的立项代号——“rea”。三个字母,连个点都没有,需求文档里翻来覆去也就一句话:做一个跨平台App,双端要一起交付。后来内部拉开架势讨论了一次,才慢慢把“re…

2026/10/12 6:27:45 阅读更多 →
Java 纯后端读写 Visio vsdx:Aspose.Diagram 实战与避坑

Java 纯后端读写 Visio vsdx:Aspose.Diagram 实战与避坑

简介:这份资源是 Aspose.Diagram 官方 Java 示例代码压缩包,面向需要在 Java 项目中处理 Visio 图表的开发者,无论入门学习还是项目集成都能用上。包内以源代码示例、依赖库、构建脚本与项目配置、测试用例及许可证说明文件为主,覆…

2026/10/12 6:27:45 阅读更多 →

最新新闻

DeepSeek开源算力地基:FlashMLA与DeepEP如何加速国产大模型?

DeepSeek开源算力地基:FlashMLA与DeepEP如何加速国产大模型?

先说明一下我的第一反应:看到“致敬,DeepSeek 最新开源的不是模型,是国产算力的地基”这个标题,我以为是又一个大模型权重开源了。结果点进仓库一看,里面没有模型文件,躺着的全是 FlashMLA、DeepEP、DeepGE…

2026/10/12 7:08:08 阅读更多 →
NumPy随机函数与广播机制:维度对齐、代码实战与踩坑全解

NumPy随机函数与广播机制:维度对齐、代码实战与踩坑全解

【day 48】随机函数与广播机制,打卡到这个组合的时候,我明显感觉身边不少同学开始卡壳了:随机函数单独用没问题,广播规则单独看也能理解,但真到写代码的时候,要么随机生成的数组形状对不上,要么…

2026/10/12 7:08:08 阅读更多 →
MTCNN人脸检测详解:P-Net、R-Net、O-Net三级网络训练与部署实战

MTCNN人脸检测详解:P-Net、R-Net、O-Net三级网络训练与部署实战

简介:面向深度学习和计算机视觉研究者,这份完整代码基于MTCNN级联卷积网络实现人脸检测,清晰覆盖P-Net候选框提议、R-Net边界框精修、O-Net关键点定位的完整流程。包体共80个文件,以Python源码为核心,包含48个py文件&a…

2026/10/12 7:08:08 阅读更多 →
物联网通信技术选型与架构设计:从链路预算到协议栈的工程实践指南

物联网通信技术选型与架构设计:从链路预算到协议栈的工程实践指南

干了十几年通信,最明显的感觉是:打电话的人越来越少,问"能不能帮我把这个箱子里的定位数据传回来"的人越来越多。物联网这个概念被炒了很多年,别人看到的是智能家居、智慧城市、大风口,我看到的是另一件事—…

2026/10/12 7:08:08 阅读更多 →
MyBatis查询功能全解析:从动态SQL到性能优化的实战指南

MyBatis查询功能全解析:从动态SQL到性能优化的实战指南

项目标题: MyBatis各种查询功能用到一定年头你就会发现,MyBatis的查询功能说难不难,说简单也绝不是写个select标签就完事。日常开发里,动态条件、分页、多表嵌套、批量操作,任何一个场景都能写出好几种不同写法,而每种…

2026/10/12 7:08:08 阅读更多 →
老毛桃UEFI启动盘v7.0:稳过Secure Boot,原生支持NVMe与Win11架构识别

老毛桃UEFI启动盘v7.0:稳过Secure Boot,原生支持NVMe与Win11架构识别

简介:老毛桃U盘启动盘制作工具(UEFI版 装机版)v7.0是一款面向电脑初学者与系统维护人员的轻量级系统辅助工具,专为快速制作兼容UEFI与传统BIOS的U盘启动盘、安装原版Windows系统及执行PE环境下的故障排查而设计。资源包共2个文件&…

2026/10/12 7:07:08 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 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/11 10:45:37 阅读更多 →
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/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练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/11 14:36:54 阅读更多 →