制造可控故障生成 Core Dump并用匹配的程序和符号定位问题前几篇已经在 QEMU ARM64 中运行了交叉编译的程序并使用 Dropbear 完成了密钥登录和 SSH 端口转发。本篇继续练习故障定位在 guest 中运行一个会主动触发SIGABRT的小程序生成 core dump再把 core 文件取回 Ubuntu使用与崩溃程序完全匹配的 ARM64 可执行文件和调试符号查看调用栈、参数及局部变量。本文的故障是专门设计的可控崩溃不修改现有debug_demo服务也不模拟真实设备故障。Core dump 可能包含崩溃进程的内存数据实验文件只用于本机练习不应公开或提交到版本库。一、实验流程与环境实验流程如下Ubuntu 20.04 x86_64 主机离线分析QEMU ARM64 guest故障注入与 core 生成Ubuntu 20.04 x86_64 主机编译与准备SSH 上传经 127.0.0.1:2222 和 QEMU hostfwdSSH 取回core_dump_demo.cBuildroot AArch64 GCC-O0 -g3 -gdwarf-4未剥离 ARM64 ELFBuild ID DWARFDropbear TCP 22公钥登录/root/core_dump_demoulimit -c unlimitedcore_pattern 指向 /root运行测试程序触发 SIGABRT/root/core.core_dump_demo.PID取回的 core 文件Buildroot 交叉 GDBsysroot: output/stagingSIGABRT、调用栈、参数和源码行项目本文沿用的设置Ubuntu 主机Ubuntu 20.04 x86_64Buildroot2025.02.8~/buildroot-2025.02.8Buildroot 输出目录~/buildroot-output-arm64-demo外部树~/buildroot-external-demo目标系统QEMU ARM64BusyBox init可写 ext2 rootfsguest SSH前文配置的 DropbearUbuntu127.0.0.1:2222转发到 guest TCP 22测试程序core_dump_demo由当前 Buildroot 工具链编译本文假设 QEMU guest 已启动、Dropbear 公钥登录可用且$HOME/.ssh/buildroot-qemu-dropbear-ed25519可用于 SSH。本文会在 QEMU guest 的/root下运行程序并生成 core不要在 Windows PowerShell、Ubuntu 主机或其他生产设备上执行故障注入命令。二、准备交叉编译器和 GDB以下命令在Ubuntu 主机执行。设置沿用前文的输出目录并查找 Buildroot 交叉编译器exportDEMO_OUT$HOME/buildroot-output-arm64-demoexportDEMO_EXT$HOME/buildroot-external-demoexportSSH_KEY$HOME/.ssh/buildroot-qemu-dropbear-ed25519exportCORE_WORK$HOME/core-dump-demomkdir-p$CORE_WORKexportCROSS_GCC$(find$DEMO_OUT/host/bin-maxdepth1\-nameaarch64-buildroot-linux-*-gcc-print-quit)if[-z$CROSS_GCC];thenprintfBuildroot AArch64 compiler not found under %s/host/bin\n$DEMO_OUT2elseexportCROSS_PREFIX${CROSS_GCC%-gcc}exportCROSS_GDB${CROSS_PREFIX}-gdbexportCROSS_READELF${CROSS_PREFIX}-readelfprintfCompiler: %s\nGDB: %s\nReadelf: %s\n\$CROSS_GCC$CROSS_GDB$CROSS_READELFif[!-x$CROSS_GCC];thenprintfBuildroot AArch64 compiler is not executable\n2elif[!-x$CROSS_READELF];thenprintfMatching readelf not found: %s\n$CROSS_READELF2elif[-x$CROSS_GDB];thenechoBuildroot AArch64 GDB is available.elseechoBuildroot AArch64 GDB is missing; build host-gdb as described below.fifi上述变量从编译器路径推导出同一工具链前缀。若未找到编译器、它不可执行或readelf缺失请先修复对应路径/工具后再继续若只缺少 GDB按下文启用并构建host-gdb。检查失败只显示提示不会用exit关闭当前终端。上述export只对当前终端有效若另开 Ubuntu 终端执行后续命令请重新执行本节的变量设置。本轮 Ubuntu 主机检查通过交叉编译器、readelf和 GDB 均可用用户名路径以$HOME简写Compiler: $HOME/buildroot-output-arm64-demo/host/bin/aarch64-buildroot-linux-gnu-gcc GDB: $HOME/buildroot-output-arm64-demo/host/bin/aarch64-buildroot-linux-gnu-gdb Readelf: $HOME/buildroot-output-arm64-demo/host/bin/aarch64-buildroot-linux-gnu-readelf Buildroot AArch64 GDB is available.GDB 必须是运行在 Ubuntu 主机上的 AArch64 交叉 GDB不是 guest 里的gdbserver。如果找不到交叉 GDB可参照前一篇 GDB 远程调试教程在 Buildroot 中启用BR2_PACKAGE_HOST_GDB并构建host-gdb。不要仅凭 Ubuntu 自带 GDB 版本较新就假定它能读取当前目标产生的 core以实际加载结果为准。三、创建带调试符号的可控故障程序在Ubuntu 主机创建测试程序cat$CORE_WORK/core_dump_demo.cEOF #include stdio.h #include stdlib.h __attribute__((noinline)) static void crash_point(int request_id, int value) { int computed_value value * 2; fprintf(stderr, controlled crash: request_id%d computed_value%d\n, request_id, computed_value); abort(); } __attribute__((noinline)) static void process_record(int request_id, int payload) { int scaled_value payload * 3; crash_point(request_id, scaled_value); } int main(void) { process_record(42, 7); return 0; } EOF找到的编译器路径填入CROSS_GCC用-O0保留直观的变量和源码行信息并生成 DWARF 调试信息与 ELF Build ID$CROSS_GCC\-Wall-Wextra-O0-g3-gdwarf-4 -fno-omit-frame-pointer\-Wl,--build-idsha1\$CORE_WORK/core_dump_demo.c\-o$CORE_WORK/core_dump_demofile$CORE_WORK/core_dump_demo若真实编译器路径不同应先更新CROSS_GCC不要继续使用不存在的示例路径。file应显示 ARM AArch64 ELF并包含debug_info、not stripped等信息。后续 GDB 必须加载这个同一次编译产生的未剥离程序重新编译或替换程序后旧 core 不一定还能与新符号对应。本轮 Ubuntu 主机实际输出如下用户名路径以$HOME简写$ file $CORE_WORK/core_dump_demo $HOME/core-dump-demo/core_dump_demo: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]f85d2d19952a5b058538905875382b8a68feb391, for GNU/Linux 6.12.0, with debug_info, not stripped这确认交叉编译成功产物是带调试信息且未剥离的 AArch64 ELF后续应继续用这个文件部署并分析对应 core。检查 Build ID$CROSS_READELF-n$CORE_WORK/core_dump_demo|grepBuild ID本轮 Ubuntu 主机实际输出如下Build ID: f85d2d19952a5b058538905875382b8a68feb391该值与前面file显示的 Build ID 一致。后续保存程序与 core 时不要混用其他构建目录中的同名文件。四、把相同程序放入 QEMU guest使用前文已配置的 Dropbear SSH从Ubuntu 主机把刚编译的二进制通过标准输入传到 guest。这样传入 guest 的就是后续在主机上提供给 GDB 的同一份 ELF不需要重建 rootfsssh-i$SSH_KEY-p2222root127.0.0.1\cat /root/core_dump_demo chmod 755 /root/core_dump_demo\$CORE_WORK/core_dump_demo如 SSH 首次提示 host key按前文流程先核对指纹输入私钥口令时终端不会回显字符。传输命令返回且没有错误后可继续在Ubuntu 主机终端通过 SSH 确认 guest 中的文件不要在 Ubuntu 主机直接执行ls /root/core_dump_demo那会检查主机自己的/rootssh-i$SSH_KEY-p2222root127.0.0.1\ls -l /root/core_dump_demo test -x /root/core_dump_demo echo executable本轮 Ubuntu 主机通过 SSH 检查 guest 的实际输出如下-rwxr-xr-x 1 root root 47384 Nov 2 11:34 /root/core_dump_demo executable输出确认测试程序已传入 QEMU guest且具有执行权限。不要在传输后重新编译 guest 上的程序。若必须重新编译应将新的 ELF 同时用于 guest 运行和 Ubuntu 上的 GDB并重新生成新的 core。五、在 guest 中启用 core dump 并触发故障以下命令全部在QEMU 目标端执行。先确认/root有足够空间然后在当前 shell 中设置 core 文件大小限制并设置明确的保存路径df-h/rootulimit-cunlimitedulimit-cprintf/root/core.%%e.%%p\n/proc/sys/kernel/core_patterncat/proc/sys/kernel/core_patternulimit -c unlimited只影响当前 shell 及之后启动的子进程因此要在这个 shell 中直接启动测试程序。core_pattern是 guest 内核的全局设置本例将文件写入可写 rootfs 的/root名称格式为core.进程名.PID。不要把此设置误写到 Ubuntu 主机的/proc。本轮 QEMU guest 的实际配置输出如下# df -h /root Filesystem Size Used Available Use% Mounted on /dev/root 51.1M 7.6M 39.3M 16% / # ulimit -c unlimited # ulimit -c unlimited # printf /root/core.%%e.%%p\n /proc/sys/kernel/core_pattern # cat /proc/sys/kernel/core_pattern /root/core.%e.%p这确认当前 shell 允许生成不受大小限制的 coreguest 内核也会将 core 写入/root。当前根文件系统报告约39.3M可用空间生成后仍需检查 core 是否完整写入。触发可控故障cd/root ./core_dump_demo程序调用abort()预期以SIGABRT终止BusyBox shell 可能显示Aborted (core dumped)。随后确认 core 文件存在ls-lh/root/core.core_dump_demo.*本轮 QEMU guest 的实际崩溃输出如下# cd /root # ./core_dump_demo controlled crash: request_id42 computed_value42 Aborted (core dumped)程序按预期因SIGABRT退出。接下来必须用ls确认 core 文件确实存在并记录其大小PID 和文件大小以现场输出为准。若没有 core 文件不要继续 GDB 分析先按第九节检查限制值、core_pattern、可写空间和内核支持。本轮 QEMU guest 的 core 文件检查结果如下# ls -lh /root/core.core_dump_demo.* -rw------- 1 root root 264.0K Nov 2 11:37 /root/core.core_dump_demo.137core 文件已生成大小为264.0Kguest PID 为137。六、将 core 文件取回 Ubuntu在Ubuntu 主机的新终端执行以下命令从 guest 取回本轮生成的 core。该终端需重新设置 SSH key 和工作目录变量CORE_NAME使用上一节现场确认的文件名exportSSH_KEY$HOME/.ssh/buildroot-qemu-dropbear-ed25519exportCORE_WORK$HOME/core-dump-demoexportCORE_NAMEcore.core_dump_demo.137if[-e$CORE_WORK/$CORE_NAME];thenprintfDestination already exists; choose another CORE_NAME.\n2elseCORE_TMP$(mktemp$CORE_WORK/.core-transfer.XXXXXX)if[-n$CORE_TMP];thenif(umask 077;ssh-i$SSH_KEY-p2222root127.0.0.1\cat /root/$CORE_NAME\$CORE_TMP);thenif[-s$CORE_TMP];thenchmod600$CORE_TMPmv$CORE_TMP$CORE_WORK/$CORE_NAME\ls-lh$CORE_WORK/$CORE_NAMEfile$CORE_WORK/$CORE_NAMEelseprintfCore transfer produced an empty file; do not analyze it.\n2rm-f$CORE_TMPfielseprintfSSH core transfer failed; do not analyze the output file.\n2rm-f$CORE_TMPfielseprintfCould not create a temporary core file under %s\n$CORE_WORK2fifi若 SSH 命令失败或文件为空检查 guest 上源 core 是否存在、Dropbear 连接和主机磁盘空间不要分析未成功取回的文件也不要把 core 上传到公共服务或直接附在问题报告中。本轮 Ubuntu 主机检查确认 core 文件大小为264Kfile的实际输出如下$CORE_WORK/core.core_dump_demo.137: ELF 64-bit LSB core file, ARM aarch64, version 1 (SYSV), SVR4-style, from ./core_dump_demo, real uid: 0, effective uid: 0, real gid: 0, effective gid: 0, execfn: ./core_dump_demo, platform: aarch64file确认该文件是 AArch64 core。core 可能包含敏感内存数据因此上述单次取回流程先设置umask 077复制后再将权限设为600。七、使用匹配的程序和符号分析在Ubuntu 主机启动 Buildroot 交叉 GDB参数依次是未剥离的 ARM64 程序和从 guest 取回的 coreexportDEMO_OUT$HOME/buildroot-output-arm64-demoexportCORE_WORK$HOME/core-dump-demoexportCORE_NAMEcore.core_dump_demo.137exportCROSS_GCC$(find$DEMO_OUT/host/bin-maxdepth1\-nameaarch64-buildroot-linux-*-gcc-print-quit)if[-n$CROSS_GCC];thenexportCROSS_GDB${CROSS_GCC%-gcc}-gdbexportCROSS_READELF${CROSS_GCC%-gcc}-readelfif[-x$CROSS_GDB];then$CROSS_GDB$CORE_WORK/core_dump_demo$CORE_WORK/$CORE_NAMEelseprintfBuildroot AArch64 GDB not found: %s\n$CROSS_GDB2fielseprintfBuildroot AArch64 compiler not found under %s/host/bin\n$DEMO_OUT2fi在(gdb)提示符逐条执行set pagination off set sysroot /home/Ubuntu用户名/buildroot-output-arm64-demo/staging sharedlibrary bt将/home/Ubuntu用户名/buildroot-output-arm64-demo/staging替换为当前 Buildroot 输出目录下的staging绝对路径。此交互顺序与本轮实际验证一致可让 GDB 找到 guest 匹配的 C 库和栈展开信息缺少 sysroot 时调用栈可能停在??或报告栈损坏。info program对 core 文件可能显示进程未在运行这不代表 core 加载失败以bt能否展开应用栈帧为准。本轮 GDB 设置 sysroot 并加载共享库后的实际 backtrace 如下。为避免暴露 Ubuntu 用户名输出中的绝对路径以$DEMO_OUT和$CORE_WORK简写#0 0x0000ffffbf277b20 in __pthread_kill_implementation () from $DEMO_OUT/staging/lib64/libc.so.6 #1 0x0000ffffbf2324dc in raise () from $DEMO_OUT/staging/lib64/libc.so.6 #2 0x0000ffffbf21e8c8 in abort () from $DEMO_OUT/staging/lib64/libc.so.6 #3 0x0000aaaacfefa818 in crash_point (request_id42, value21) at $CORE_WORK/core_dump_demo.c:12 #4 0x0000aaaacfefa848 in process_record (request_id42, payload7) at $CORE_WORK/core_dump_demo.c:20 #5 0x0000aaaacfefa868 in main () at $CORE_WORK/core_dump_demo.c:25调用栈从abort()完整展开到crash_point、process_record和main参数值也与测试程序的计算过程一致。从bt输出中找到crash_point对应的帧号再选中它并查看参数、局部变量和源码frame crash_point 的帧号 info args info locals list本轮在crash_point帧中的实际输出如下用户名路径以$CORE_WORK简写(gdb) frame 3 #3 0x0000aaaacfefa818 in crash_point (request_id42, value21) at $CORE_WORK/core_dump_demo.c:12 12 abort(); (gdb) info args request_id 42 value 21 (gdb) info locals computed_value 42 (gdb) list 7 int computed_value value * 2; 8 9 fprintf(stderr, 10 controlled crash: request_id%d computed_value%d\n, 11 request_id, computed_value); 12 abort(); 13 } 14 15 __attribute__((noinline)) 16 static void process_record(int request_id, int payload)实测确认request_id42、value21、computed_value42源码位置为abort()调用行。帧号不要照抄示例必须以当前bt输出为准。为什么必须匹配Core 记录崩溃进程的寄存器、内存映射和部分内存状态但不会替代原程序的调试符号。GDB 需要正确的 ARM64 ELF 来解释地址、函数名、行号及变量。若同名程序来自不同构建符号可能错误地映射到不相干的代码。本实验通过同一个$CORE_WORK/core_dump_demo文件直接传给 guest 并在主机侧用它分析保持二者一致。若之后重新编译、strip 或替换文件要重新部署程序、重新触发崩溃并保存对应 core。可用以下命令查看候选程序的 Build ID并与记录的构建产物核对$CROSS_READELF-n$CORE_WORK/core_dump_demo|grepBuild ID不要只根据文件名、时间戳或相同源码判断二进制匹配。八、常见问题现象优先检查程序显示Aborted但没有 core 文件当前 shell 的ulimit -c是否为unlimitedcore_pattern是否指向可写路径/root是否有空间检查内核 core dump 支持core_pattern以 开头core 文件名和预期不同查看/proc/sys/kernel/core_pattern%e是进程名%p是 guest PIDGDB 报not in executable format或架构不匹配使用 Buildroot 的 AArch64 交叉 GDB并检查file输出确认程序和 core 均属于 AArch64GDB 只显示地址或??确认加载的是未剥离且与崩溃程序同次构建的 ELF系统库符号缺失时应用自己的符号仍可单独检查变量显示不可用检查是否使用匹配的-O0 -g3ELF崩溃位置和变量生命周期也会影响可见性core 文件非常大或磁盘不足core 包含进程地址空间内容确认 guest 可用空间只在实验后保留所需文件并安全清理SSH 取回 core 失败确认 QEMUhostfwd、Dropbear 和公钥登录仍可用并使用 guest 中实际 core 文件名九、实验边界与安全注意事项本篇演示的是 Linux 用户态进程在 QEMU ARM64 中生成 core并在 Ubuntu 主机使用匹配的程序和符号离线分析。它不覆盖内核崩溃、JTAG、真实板卡外设、生产服务的自动 core 收集或 systemd-coredump。Core 文件可能包含进程内存中的口令、令牌、密钥、用户数据和网络内容。示例用 root 在 guest/root下生成 core 只是为了简化权限和传输不要把 core、私钥或未经脱敏的调试输出提交到代码仓库或公开发布。实验结束后在 QEMU guest 和 Ubuntu 主机分别删除本次专用程序与 core 文件确认不会误删其他实验文件。十、小结通过在 Buildroot 交叉编译一个带调试信息的 ARM64 程序、在 QEMU guest 设置 core dump 限制并触发可控SIGABRT可以得到用户态 core 文件。将 core 取回 Ubuntu 后使用同一次构建产生的未剥离 ELF 和 AArch64 交叉 GDB即可从崩溃现场检查信号、调用栈及应用变量。可靠分析的关键不是文件名相同而是 core、运行程序和调试符号确实对应同一构建。参考资料Linuxcore(5)手册Linuxgetrlimit(2)手册GDBCore File GenerationGDBFilesBuildroot 官方手册
企业数字化 ERP 产品动态
相关推荐
别再只会“帮我写个周报”了:用《千问高效办公的方法和技巧》把 AI 变成办公副驾 千问高效办公的方法和技巧【行情 报价 价格 评测】-京东
这本书的全文试读,点击下面链接:
王银辉AI办公新思路《千问高效办公的方法和技巧》全文分享~_千问办公教程-CSDN博客
在 CSDN 混的兄弟应该都有这种体感:
写代码我们用 Copilot&am… · 2026/11/5 5:33:09
STM32F407六路串口DMA并发收发实战 简介:本资源是一份面向嵌入式开发工程师与STM32进阶学习者的实战型实验工程,聚焦STM32F407芯片在高并发串口通信场景下的DMA高效应用,解决多路串口数据收发导致CPU负载过高、实时性不足等典型痛点,适用于工业网关、多传感器汇聚终… · 2026/11/5 6:14:22
二叉搜索树(BST)原理与C++实现指南 1. 二叉搜索树的核心价值与应用场景二叉搜索树(Binary Search Tree, BST)作为数据结构领域的经典之作,在C程序员的成长道路上扮演着重要角色。我第一次在游戏排行榜功能中实现BST时,深刻体会到它O(log n)时间复杂度带来的性能优势… · 2026/11/5 6:14:22
链表去重算法解析与双指针法实现 1. 问题分析与解法思路链表去重是算法学习中的经典问题,LeetCode第83题"删除排序链表中的重复元素"要求我们保留每个元素的一次出现,删除所有后续重复项。面对这个问题,我们需要深入理解链表的特性和操作方式。链表与数组最大的区别… · 2026/11/5 6:14:22
大专生如何在大数据行业突围:就业现状与技能提升策略 1. 大数据行业就业现状全景扫描2026届大专层次大数据科学专业毕业生面临的就业形势,需要从行业需求、学历门槛、技能匹配三个维度综合分析。当前大数据行业已度过爆发期进入理性发展阶段,头部企业招聘门槛持续抬高,但细分领域仍存在大量机会。… · 2026/11/5 6:14:22
笔记本部署VMware ESXi 6.0实战:网卡驱动与避坑指南 笔记本电脑部署VMware ESXi 6.0系统这件事,我第一次干的时候踩的坑比想象中多得多。表面上看,这不过是把一张ISO镜像写进U盘、改个启动项、点几下下一步的事,但真正动手才会发现:笔记本的网卡驱动、电源管理、存储控制器、UEFI与L… · 2026/11/5 6:14:22
2026年Typora依然是最佳Markdown编辑器的三大理由 1. 为什么在2026年我依然选择Typora作为一个重度Markdown用户,我几乎尝试过市面上所有主流编辑器。从早期的Mou到后来的VS CodeMarkdown插件,从Notion到Obsidian,最终在2026年这个时间节点,我依然选择回归Typora。这不是简单的怀旧… · 2026/11/5 6:13:22
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
多智能体协作系统架构设计与实践优化 1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39
大模型系统性入门实战指南:从原理到部署微调的全链路路线 提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39
鸿蒙文件系统沙箱机制解析与开发实践 1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38