PE文件节表合并实战:从原理到避坑指南

1. 从一个真实需求说起:为什么要动PE文件的节表

搞过Windows平台逆向或者安全分析的朋友,大概率都遇到过这样的场景:手上拿到一个PE文件,用工具一看,节表里躺着七八个节,.text、.rdata、.data、.rsrc、.reloc,每个节都占着独立的文件偏移和对齐空间。文件本身可能只有几百KB的逻辑内容,但实际体积却膨胀到好几MB,因为每个节都要按FileAlignment对齐,尾部填充了大量零字节。

这时候如果只是做静态分析,倒也无所谓。但如果你的目标是给文件"瘦身",或者想把多个节的内容整合到一块连续的内存区域里方便后续处理,又或者你在做壳的开发、代码注入、特征码提取这类工作,合并节就成了一个绕不开的操作。

所谓合并节,本质上是把PE文件中两个或多个节的内容拼接到一起,合并成一个新的节,同时更新节表、数据目录、重定位表等所有引用了原节地址的结构。听起来简单,但PE格式的坑非常多——文件对齐和内存对齐的差异、RVA和文件偏移的换算、数据目录里那些指向节内部的指针、重定位项里记录的绝对地址,任何一处没处理好,文件就直接跑不起来。

这篇文章我会从PE节表的基本结构讲起,把合并节的完整流程拆开揉碎,包括每一步为什么要这么做、参数怎么算、哪些地方最容易翻车。适合有一定PE基础、想深入理解文件格式细节的读者,也适合正在做二进制工具开发、需要实现节合并功能的同学参考。

2. 动手之前必须搞清楚:PE节表的底层结构

2.1 节表在文件中的位置和数量

PE文件的结构从DOS头开始,经过DOS Stub,到达NT Headers。NT Headers里包含File Header和Optional Header。File Header中有一个字段叫NumberOfSections,它决定了节表里有多少个节表项。节表紧跟在Optional Header之后,每个节表项占40字节,结构定义如下:

typedef struct _IMAGE_SECTION_HEADER { BYTE Name[8]; // 节名,8字节,不足补零 union { DWORD PhysicalAddress; // 物理地址,通常不用 DWORD VirtualSize; // 节在内存中的实际大小 } Misc; DWORD VirtualAddress; // 节在内存中的RVA DWORD SizeOfRawData; // 节在文件中对齐后的大小 DWORD PointerToRawData; // 节在文件中的偏移 DWORD PointerToRelocations; // 重定位偏移,通常为0 DWORD PointerToLinenumbers; // 行号偏移,通常为0 WORD NumberOfRelocations; // 重定位数量 WORD NumberOfLinenumbers; // 行号数量 DWORD Characteristics; // 节属性标志 } IMAGE_SECTION_HEADER;

这里有几个关键点需要特别注意。VirtualSize是节在内存中的实际数据大小,而SizeOfRawData是节在文件中经过FileAlignment对齐后的大小。这两个值通常不相等,因为文件对齐粒度(一般是0x200)和内存对齐粒度(一般是0x1000)不同。合并节的时候,新节的VirtualSize应该是各原节VirtualSize之和(考虑内存对齐后的),而SizeOfRawData则要按文件对齐重新计算。

2.2 文件对齐与内存对齐的换算关系

PE文件有两个对齐参数,都在Optional Header里:

  • FileAlignment:文件中对齐的粒度,典型值0x200(512字节)
  • SectionAlignment:内存中对齐的粒度,典型值0x1000(4096字节)

一个节在文件中的偏移PointerToRawData必须是FileAlignment的整数倍,在内存中的RVAVirtualAddress必须是SectionAlignment的整数倍。这就导致了一个问题:当你把两个节合并时,第二个节的数据在文件中的起始位置需要重新计算,而它在内存中的RVA也要相应调整。

举个具体的例子。假设有两个节:

节名VirtualAddressVirtualSizePointerToRawDataSizeOfRawData
.text0x10000x23450x4000x2600
.rdata0x40000x12340x2A000x1400

合并后新节的VirtualAddress取.text的0x1000,VirtualSize需要计算两个节内存范围的并集大小。.text占用的内存范围是0x1000到0x1000+0x2345=0x3345,.rdata占用的范围是0x4000到0x4000+0x1234=0x5234。合并后新节从0x1000开始,到0x5234结束,所以VirtualSize至少是0x4234。但还要考虑内存对齐,新节的VirtualSize应该按SectionAlignment向上取整到0x5000。

文件中的SizeOfRawData则是把两个节的原始数据拼接后,按FileAlignment向上取整。.text的原始数据是0x2600字节,.rdata是0x1400字节,拼接后是0x3A00字节,按0x200对齐后是0x3A00(刚好整除)。

2.3 数据目录:合并节时最容易被忽略的雷区

Optional Header里有16个数据目录项,每个8字节,包含一个RVA和一个大小。这些目录项指向节内部的数据结构,比如导入表、导出表、资源表、重定位表等。合并节之后,这些目录项的RVA必须全部更新,否则系统加载器找不到对应的数据。

数据目录的更新逻辑是:对于每个目录项,判断它的RVA落在哪个原节的范围内,然后计算它相对于该节起始地址的偏移,再加上新节的起始RVA。如果目录项的RVA不在任何被合并的节内,则不需要修改。

这里有个细节:有些目录项的RVA可能指向节与节之间的空隙,或者指向文件头区域。对于指向文件头区域的目录项(比如安全目录),合并节不影响它们,不需要修改。对于指向被合并节内部的目录项,必须逐个更新。

3. 合并节的完整操作流程

3.1 选择要合并的节并确定新节布局

第一步是决定合并哪些节。通常有两种策略:合并所有节为一个节,或者只合并特定的几个节。合并所有节最简单,但会丢失节属性信息(比如代码节的执行权限、数据节的可写权限),可能影响程序运行。更稳妥的做法是合并属性相同的节,比如把.text和.rdata合并(都是只读可执行),把.data和.bss合并(都是可读写)。

确定要合并的节之后,需要计算新节的布局参数:

  • 新节的VirtualAddress:取第一个被合并节的VirtualAddress
  • 新节的VirtualSize:所有被合并节内存范围的并集大小,按SectionAlignment向上取整
  • 新节的PointerToRawData:取第一个被合并节的PointerToRawData
  • 新节的SizeOfRawData:所有被合并节原始数据拼接后的大小,按FileAlignment向上取整
  • 新节的Characteristics:取所有被合并节属性的并集,或者取第一个节的属性

这里有个容易踩的坑:如果被合并的节在内存中的RVA不是连续的(中间有其他节隔着),那么合并后新节的VirtualSize会包含那些空隙。这些空隙在文件中也会被填充为零字节,导致文件体积反而增大。所以合并节之前要检查各节的RVA是否连续,如果不连续,要么调整节顺序,要么接受体积增大的代价。

3.2 拼接节数据并处理文件偏移

确定了新节布局之后,就可以开始拼接数据了。具体操作是:

  1. 在内存中分配一块缓冲区,大小为新节的SizeOfRawData
  2. 将第一个被合并节的原始数据复制到缓冲区起始位置
  3. 将第二个被合并节的原始数据复制到缓冲区中,偏移量为第二个节相对于新节起始地址的偏移
  4. 以此类推,直到所有被合并节的数据都复制完毕
  5. 缓冲区中未被数据覆盖的部分填充为零

这里的关键是计算每个原节数据在新缓冲区中的偏移。对于第i个被合并节,其数据在新缓冲区中的偏移等于:

offset_i = VirtualAddress_i - new_VirtualAddress

注意这个偏移是基于RVA计算的,而不是基于文件偏移。因为节在内存中的布局和文件中的布局可能不同,必须用RVA差值来确定数据在新节中的位置。

复制完成后,需要把新节的数据写回文件。写入位置是new_PointerToRawData,写入长度是new_SizeOfRawData。如果新节的数据比原来所有节的数据总和还大(因为填充了空隙),文件体积会增大,这是正常的。

3.3 更新节表和数据目录

数据拼接完成后,需要更新节表。具体操作是:

  1. 修改File Header中的NumberOfSections,减少被合并的节数量
  2. 用新节的节表项替换第一个被合并节的节表项
  3. 删除其他被合并节的节表项
  4. 如果被合并的节不是连续的,需要移动后续节表项填补空缺

更新数据目录时,需要遍历所有16个目录项,对于每个目录项:

  • 如果VirtualAddress为0,跳过
  • 如果VirtualAddress落在被合并节的范围内,计算它相对于第一个被合并节起始地址的偏移,然后加上新节的VirtualAddress
  • 如果VirtualAddress不在被合并节范围内,保持不变

这里有个细节:数据目录项的大小字段不需要修改,因为数据本身没有移动,只是地址变了。

3.4 重定位表的修正

如果PE文件包含重定位表(通常是DLL或者开启了ASLR的EXE),合并节之后必须修正重定位项。重定位表中记录了所有需要修正的绝对地址,每个重定位项包含一个RVA。如果这个RVA落在被合并节的范围内,就需要更新为新节中的对应RVA。

重定位表的修正逻辑和数据目录类似:对于每个重定位项,判断其RVA是否在被合并节的范围内,如果是,则计算偏移并更新。但重定位表本身也可能在被合并的节内,这就形成了一个循环依赖:修正重定位表需要知道重定位表的新地址,而重定位表的新地址又取决于重定位表本身的内容。

解决这个问题的办法是分两步走:先修正重定位表中指向其他数据的项,再修正重定位表自身的地址。或者更简单粗暴:先把重定位表复制到新节的末尾,修正所有重定位项,然后再把修正后的重定位表写回。

4. 那些年我踩过的坑:合并节的常见问题与排查

4.1 文件能打开但运行崩溃:对齐问题排查

合并节之后最常见的问题就是文件能正常打开,但一运行就崩溃。这种情况十有八九是对齐没处理好。具体表现可能是:

  • 新节的VirtualSize没有按SectionAlignment对齐,导致后续节的内存地址错位
  • 新节的SizeOfRawData没有按FileAlignment对齐,导致文件读取越界
  • 数据目录项的RVA没有正确更新,指向了错误的内存位置

排查这类问题,我通常用PE解析工具(比如自己写一个简单的解析脚本)把合并后的文件结构打印出来,逐项检查:

import struct def parse_pe_sections(filepath): with open(filepath, 'rb') as f: data = f.read() # 读取DOS头,获取NT头偏移 e_lfanew = struct.unpack_from('<I', data, 0x3C)[0] # 读取File Header num_sections = struct.unpack_from('<H', data, e_lfanew + 6)[0] opt_header_size = struct.unpack_from('<H', data, e_lfanew + 20)[0] # 读取Optional Header中的对齐参数 file_align = struct.unpack_from('<I', data, e_lfanew + 24 + 36)[0] sect_align = struct.unpack_from('<I', data, e_lfanew + 24 + 32)[0] print(f"FileAlignment: 0x{file_align:X}") print(f"SectionAlignment: 0x{sect_align:X}") print(f"NumberOfSections: {num_sections}") # 节表起始位置 sect_table = e_lfanew + 24 + opt_header_size for i in range(num_sections): offset = sect_table + i * 40 name = data[offset:offset+8].rstrip(b'\x00').decode('ascii', errors='ignore') virt_size = struct.unpack_from('<I', data, offset + 8)[0] virt_addr = struct.unpack_from('<I', data, offset + 12)[0] raw_size = struct.unpack_from('<I', data, offset + 16)[0] raw_addr = struct.unpack_from('<I', data, offset + 20)[0] print(f"\nSection: {name}") print(f" VirtualAddress: 0x{virt_addr:X}") print(f" VirtualSize: 0x{virt_size:X}") print(f" PointerToRawData: 0x{raw_addr:X}") print(f" SizeOfRawData: 0x{raw_size:X}") # 检查对齐 if virt_addr % sect_align != 0: print(f" [ERROR] VirtualAddress not aligned to 0x{sect_align:X}") if raw_addr % file_align != 0: print(f" [ERROR] PointerToRawData not aligned to 0x{file_align:X}")

运行这个脚本,如果看到[ERROR]输出,就说明对齐有问题,需要回去检查合并时的计算。

4.2 数据目录指向错误:导入表失效的修复

另一个常见问题是导入表失效。导入表的RVA在数据目录的第2项(索引1),如果这个RVA没有正确更新,程序启动时就找不到导入函数,直接报错退出。

修复方法是手动更新导入表的RVA。但要注意,导入表本身是一个结构数组,每个IMAGE_IMPORT_DESCRIPTOR包含一个指向函数名数组的RVA(OriginalFirstThunk)和一个指向函数地址数组的RVA(FirstThunk)。这些RVA也需要更新,因为它们指向的字符串和地址数组可能在被合并的节内。

我遇到过一次比较隐蔽的情况:导入表的RVA更新了,但OriginalFirstThunk指向的字符串数组没有更新,导致程序能启动但调用某些API时崩溃。排查这种问题需要递归地检查所有指针,确保它们都指向正确的位置。

4.3 重定位表修正不完整:ASLR开启后的随机崩溃

如果合并节之后没有正确修正重定位表,程序在开启ASLR的系统上会随机崩溃。因为每次加载时基址不同,重定位项指向的绝对地址需要动态修正,如果重定位表本身的内容错了,修正结果就是错的。

排查重定位问题,可以用工具查看重定位表的内容,对比合并前后的差异。重点检查:

  • 重定位表的数据目录项RVA是否正确
  • 重定位块中的每个项是否指向了正确的RVA
  • 重定位块的大小是否覆盖了所有需要修正的地址

有个经验:如果程序在关闭ASLR时能正常运行,开启ASLR后崩溃,那基本可以确定是重定位表的问题。

5. 进阶技巧:自动化合并与验证

5.1 用脚本实现批量合并

手动合并节容易出错,尤其是当文件有多个节需要合并时。我通常写一个Python脚本来自动化这个过程。核心逻辑是:

  1. 解析PE文件,读取节表和数据目录
  2. 确定要合并的节,计算新节布局
  3. 拼接节数据,更新节表和数据目录
  4. 修正重定位表
  5. 写回文件

脚本的关键是要处理各种边界情况,比如节数量为1时不需要合并、数据目录项为0时跳过、重定位表不存在时跳过等。

5.2 合并后的验证清单

合并完成后,必须做以下验证:

验证项检查方法预期结果
节数量读取File Header等于原数量减去被合并数量
节对齐检查VirtualAddress和PointerToRawData分别按SectionAlignment和FileAlignment对齐
数据目录遍历16个目录项RVA都指向有效位置
导入表用工具查看导入函数函数列表完整,地址正确
重定位表检查重定位项RVA都在有效范围内
文件运行实际执行功能正常,无崩溃

5.3 一个实用的调试技巧

如果合并后的文件运行异常,但又看不出哪里有问题,可以尝试用调试器加载文件,在入口点下断点,单步执行。观察每一步的内存访问,看是否有访问无效地址的情况。通常崩溃点附近就是问题所在。

另外,可以对比合并前后的文件,用二进制差异工具查看哪些字节发生了变化。重点关注节表区域、数据目录区域和重定位表区域。如果发现意外的变化,说明合并逻辑有bug。

6. 个人经验:合并节这件事,细节决定成败

做了这么多次节合并,我最大的体会是:PE文件格式的容错性其实很低,任何一个字段算错都可能导致文件无法运行。但反过来,只要你把每个字段的含义和对齐规则搞清楚,合并节本身并不复杂。

几个我反复验证过的经验:

第一,永远不要相信手动计算。写个脚本自动算,算完再人工核对一遍。我见过太多因为手算偏移量算错一位导致文件报废的情况。

第二,先备份再操作。合并节是不可逆的(至少在没有原始文件的情况下),操作前一定要保留原始文件的副本。

第三,分步验证。不要一次性合并所有节,先合并两个节,验证通过后再合并更多。这样出问题时容易定位。

第四,关注数据目录。数据目录是合并节时最容易出问题的地方,因为它的更新逻辑比较绕,而且一旦出错就是运行时崩溃,不像节表错误那样容易被发现。

最后分享一个我常用的检查方法:合并完成后,用PE解析工具把文件的所有结构打印出来,然后逐项对照原始文件,确认只有预期的字段发生了变化。这个方法虽然笨,但非常有效,能发现很多隐蔽的问题。

相关新闻

动作空间思维链:ACoT-VLA让机器人先“排练”再动手

动作空间思维链:ACoT-VLA让机器人先“排练”再动手

ACoT-VLA&#xff0c;这个缩写最近在我所在的机器人学习圈子里被反复提及。它把大语言模型那套“下结论前先想几步”的思维链机制&#xff0c;从文本空间搬到了VLA&#xff08;视觉-语言-动作模型&#xff09;真正的动作空间里&#xff1a;让机器人在动手之前&#xff0c;先在内…

2026/10/21 7:43:04 阅读更多 →
自研分布式定时任务框架:从时间轮到OpenAPI异步调用的实践

自研分布式定时任务框架:从时间轮到OpenAPI异步调用的实践

定时任务这块&#xff0c;我相信每个后端团队都有过一段折腾史。早些年简单业务我直接塞进程里跑&#xff0c;后来规模大了开始用Quartz集群&#xff0c;再后来发现调度逻辑和业务代码越缠越紧&#xff0c;加上产品那边动不动就提“帮我跑个数据&#xff0c;明天给我结果”&…

2026/10/21 7:42:04 阅读更多 →
AI检测率过高怎么办?8款降AI率工具实测对比与写作攻略

AI检测率过高怎么办?8款降AI率工具实测对比与写作攻略

咱们先把话说在前面&#xff1a;这一两年&#xff0c;AI检测率这件事&#xff0c;快把专科生的毕业季逼成“渡劫现场”了。我接触过的学生里&#xff0c;十个有八个写过论文之后第一反应不是查重&#xff0c;而是先跑一遍AIGC检测&#xff0c;看到那个红彤彤的百分比&#xff0…

2026/10/21 7:42:04 阅读更多 →

最新新闻

充电桩漏电流防护:检测模组原理、选型与现场故障排查

充电桩漏电流防护:检测模组原理、选型与现场故障排查

去年年底,某充电站连续出现不明跳闸,运维群里吵了三天的结论是“这批桩的漏保质量有问题”。等我们带着示波器到现场,才发现真正的原因既不是漏保故障,也不是桩本体故障——而是充电模块启动瞬间产生的共模电流,直接顶翻了剩余电流保护阈值。这个案例我一直记着,因为它把充电桩…

2026/10/21 8:16:32 阅读更多 →
毕业知识串讲方法论:把散落知识点串成知识链

毕业知识串讲方法论:把散落知识点串成知识链

毕业知识串讲每年到了毕业季&#xff0c;总有学生抱着厚厚一摞笔记跑过来问我&#xff1a;"老师&#xff0c;我这四年学的东西东一块西一块&#xff0c;脑子里全是一堆孤立的知识点&#xff0c;考试的时候能写出来&#xff0c;但让我整体说说这门课讲了什么&#xff0c;我…

2026/10/21 8:16:32 阅读更多 →
从零搭建代码评审自动化流程:静态分析与规则引擎实战

从零搭建代码评审自动化流程:静态分析与规则引擎实战

1. 从零搭建一套代码评审自动化流程的完整思路代码评审这件事&#xff0c;做过团队协作开发的人都懂——它重要&#xff0c;但极其消耗精力。一个中等规模的研发团队&#xff0c;每天产生的合并请求少则十几个&#xff0c;多则几十个&#xff0c;如果全靠人工逐行去看&#xff…

2026/10/21 8:16:32 阅读更多 →
Selenium数据构造:突破测试自动化卡点的关键实践

Selenium数据构造:突破测试自动化卡点的关键实践

1.1 测试数据构造为什么成为卡点&#xff0c;而不只是“点几下按钮”1.2 Selenium的正确打开方式&#xff1a;数据构造并不是“不务正业”

2026/10/21 8:16:32 阅读更多 →
汽车电子整流检波电路设计:从原理到EMC整改实践

汽车电子整流检波电路设计:从原理到EMC整改实践

简介&#xff1a;面向汽车电子电器与电磁兼容&#xff08;EMC&#xff09;领域的整流检波电路专业参考文档《几种典型的整流检波电路.pdf》&#xff0c;系统梳理半波整流、全波整流、桥式整流三种典型整流电路&#xff0c;以及包络检波、同步检波等常用检波电路的原理、特点与适…

2026/10/21 8:16:32 阅读更多 →
大模型岗面试风向:从八股到工程实战的考察要点与答题思路

大模型岗面试风向:从八股到工程实战的考察要点与答题思路

大模型岗的面试风向&#xff0c;这两年变化比很多人想象中要快得多。早几年问的是“Transformer里Attention怎么算”&#xff0c;现在再拿这套去准备&#xff0c;大概率会在二面三面栽跟头。我自己最近半年密集参与了多个大模型相关岗位的面试&#xff0c;也帮团队做过不少轮技…

2026/10/21 8:15:29 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 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/20 10:02:08 阅读更多 →
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/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练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/20 9:50:45 阅读更多 →