FilesLast commitLast update
1 day ago
5 days ago
5 days ago
3 months ago
5 days ago
5 days ago
4 months ago
README

单元测试

概述

本项目为 drivers 和 libraries 目录下的所有 cpp 文件扩展测试用例,目标使行覆盖率大于90%。

LLDP MAC 字段长度回归

问题与当前行为

生产代码见 LLDP 解析器,用例见 LLDP 单元测试。本节仅涉及机箱 ID 和端口 ID 的 MAC 子类型。 以下长度均指去掉 1 字节子类型后的 MAC 数据长度,不能把子类型算进 6 字节 MAC。

原代码直接让 mac_addr_to_hex_string 读取 6 字节;输入只有 0~5 字节时, 读取会越过字段有效长度。修复在 parse_chassis_id_tlv 和 parse_port_id_tlv 调用该函数前判断 info_string_data.size() != LLDP_MAC_ADDRESS_LEN, 短字段和超长字段均抛出 std::runtime_error,避免超长输入被静默截取。 LLDP_MAC_ADDRESS_LEN 是本文件内值为 6 的编译期常量,校验与格式化共用它。 这里的异常表示立即退出当前解析路径,由调用方的 try/catch 接收错误; 与 C 中检查长度后返回错误码的作用相近,但它不是普通的函数返回值。

MAC 数据长度 当前行为及覆盖
0~5 字节 读取前报错;两个短字段用例各遍历全部 6 种长度。
恰好 6 字节 正常解析;已有机箱/端口 MAC 用例均验证 11:22:33:44:55:66。
大于 6 字节 读取前报错;两个超长字段用例各覆盖 7、8、255 字节。255 是通用 ID TLV 长度上限扣除子类型字节。

新增用例名称:

  • lldp_test.parse_lldp_tlv_chassis_id_rejects_short_mac
  • lldp_test.parse_lldp_tlv_port_id_rejects_short_mac
  • lldp_test.parse_lldp_tlv_chassis_id_rejects_long_mac
  • lldp_test.parse_lldp_tlv_port_id_rejects_long_mac

现有合法输入用例为 lldp_test.parse_lldp_tlv_chassis_id_mac 和 lldp_test.parse_lldp_tlv_port_id_mac。本次只收紧 MAC 子类型,其他子类型的长度范围保持不变。 注意 parse() 只拆分和校验 TLV 外层结构;上述 MAC 语义校验发生在 parse_lldp_tlv()。

复现与重跑入口

在仓库根目录准备好项目要求的 Bingo、Conan 和编译依赖后执行:

bingo test -ut -jit
bingo test -ut -jit -as

第一条为普通单元测试入口,第二条增加 ASAN 配置;均未限制为 LLDP 定向执行。 测试入口 使用自己的 gtest_filter_map 组织 GTest 参数, 未见将 Bingo 的 -f 转交给这些二进制,因此这里不使用 -f 过滤 C++ 用例。 重跑后应确认上述用例在原始日志中实际出现 [ RUN ] 和 [ OK ],不能只看汇总绿灯。 同时记录源码提交、依赖/构建配置、实际用例计数及 sanitizer 原始输出。

本次验证与历史记录

2026-10-13 对本次源码进行了独立的 LLDP 定向构建:直接编译仓库的 libraries/lldp/lldp.cpp、tests/libraries/lldp/test_lldp.cpp 和 tests/main.cpp, 复用本机预编译 libmcpp/test_utilities、系统 GTest 1.14.0 和 Lua 5.4。 这不是全项目 Bingo 构建,也没有重新编译或为依赖库增加 sanitizer 插桩。

  • 旧实现加两个超长用例:两项均失败,7、8、255 字节均未按预期抛出异常。
  • 修复后 GCC 普通构建:LLDP 39/39 通过,无跳过、失败。
  • 修复后 Clang 18 ASAN/UBSAN 构建:LLDP 39/39 通过,无跳过、失败; ASAN_OPTIONS=detect_leaks=1:halt_on_error=1、 UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1,无 sanitizer 错误。

原始日志、XML、构建配置和二进制哈希保存在仓库外的任务记录中。 本次未执行全仓测试、目标板测试或长时间压力测试。

2026-09-24 的历史基线为 99e5b080243ab685e6b26a390dfc424308b735b4, 修复提交为 fc6a10dbec1454a7df994aea3ed527c43b27d895。 在 ASAN 编译的原代码上,两个新增用例均因“预期抛出异常,实际未抛出”失败, 退出码为 1;没有复现 ASAN overflow 报告。 std::string 可能把短内容存放在对象自己的缓冲区中:超过有效长度不一定越过 ASAN 检查的分配边界,因此不能把此次行为回归写成已复现分配边界溢出。

历史修复后使用同一 ASAN 构建配置,运行 GTEST_FILTER='lldp_test.*', 并设置 ASAN_OPTIONS='detect_leaks=1:halt_on_error=1',记录为 37/37 通过、 退出码 0、无 ASAN/LSAN ERROR。该记录属于上述历史提交,不是当前文档提交的新测试结果。 历史全量构建曾因资源竞争中断,随后只完成 LLDP 定向验证,未完成全仓 ASAN 测试。

这些记录不能证明全库内存问题清零,也不能替代后续源码变更后的重新验证。


2026-10-14:内存边界与异步生命周期修复拆分

原 !1307 汇总了协议边界、对象寿命、测试时序和本地构建工具等不同问题。历史压力日志出现过间歇性崩溃,源码检查又发现若干可独立修复的越界及异步收尾路径。不能据此把每条风险都认定为某次历史崩溃的已证实根因,因此本轮按问题类型拆成十个独立 PR,让代码、回归和证据范围可以分别核对。

下表是本轮修复范围索引;PR 中的源码与提交对应的验证报告才是逐项验收依据。各分支最初独立建立在同一主线基线上,后续主线变化需要正常整合。

主题 问题触发与关键修复 对应 PR
1. NCSI 回复与字段复制 短回复、设备声明的过长 MAC/SN/PN、分帧日志长度与真实 payload 不一致,可能使读取或复制越界。先校验最小头、固定字段容量、累计偏移和实际接收量,再取字段;检查复制结果,保留设备不在位短回复及旧日志命令回退。异常输入会返回失败,属于运行时代码修复。 !1326
2. 总线、芯片与 SMC 缓冲区 CAN/I2C 写入长度超过输入向量或缓冲区、芯片短回复、非法 offset 宽度以及 SMC 帧长不一致,可能越界或触发超宽移位。补齐发送/接收边界,小端寄存器按字节解码;保留主线 LocalBus 已提取位域的语义,避免二次掩码。非法请求和短回复的错误返回是实际行为变化。 !1335
3. CSR1 调度任务与宿主引用 空闲注册也持有宿主,会阻止宿主进入析构注销;回调内注销又可能等待自身。空闲状态保存弱引用,运行期间才持有宿主;重注册复用仍运行的状态,防止同一任务重入。Aggregate 仅为托管宿主保存弱引用,避开最终释放及未托管对象的失效计数访问。 !1329
4. CSR1 内嵌接口定时器 总线、芯片和 Scanner 的接口存储结束后,晚释放的 timer 仍可能回访原父接口;活动回调也可能继续使用接口成员。timer 改为无父对象、成员持有并显式绑定执行器;析构/替换先停止任务,保留单次 timer 句柄用于后续收尾。 !1330
5. 网卡定时器启停 Broadcom 重复启动时可能先覆盖协议或包编号,而旧回调仍在使用接口;Hisi 等路径也需明确停止与成员销毁顺序。启动前停止旧任务,再更新依赖并建立新 timer;配套夹具等待真实完成事件。它包含正式网卡启停行为修复,不只是延长测试等待时间。 !1331
6. NVMe 遥测与升级收尾 Huawei/Samsung 遥测回调及升级线程使用宿主或兄弟接口;先拆成员再收尾可能访问失效存储。设备析构入口先停任务,Huawei 升级线程在依赖成员销毁前 join;timer 显式使用宿主或接口配置的执行器。 !1332
7. PRBS 打包字段与 SMART 数值 直接把打包回复地址当 uint32_t* 读取可能未对齐;128 位 PowerOnHours 高半部移入 uint64_t 会发生移位数不小于64的未定义行为。PRBS 经 memcpy 取对齐局部值再转小端;SMART 经公共 helper 按小端组合完整128位整数,再一次转换为既有 double 属性,避免分别转换高低半部引入额外舍入。浮点精度限制仍存在,不能宣称精确保存全部128位整数,详见数值解码评审说明。 !1333
8. 测试夹具与异步等待 Scanner、网卡、DERA、RAID 等夹具依赖固定 sleep 或未清理回调,可能在异步工作完成前释放对象。使用完成通知和明确收尾,修正测试释放顺序、恢复相关覆盖并补回 SMBus 帧事务测试。本主题只改测试。 !1334
9. 串行压力统计与说明 单看 Meson 退出成功不能证明所有配置程序和 gtest 用例完整执行。保存逐轮日志与 summary,严格拒绝跳过、缺失和未完成;区分超时与中断,避免重复长用例名结束标记误补短用例名。工具不安装 sanitizer 抑制,使用方法见下一节。 !1327
10. 本地 Lua 工具链选择 本地 smart_build.sh 只看到 lua.h 就可能采用不完整 temp 工具链,切换来源还可能残留可选头或错误复用缓存。统一检查四个核心头,成对选择头/库,并将兼容头纳入软链接清理和缓存指纹。正式 Bingo/Conan recipe 与 CI 构建入口不调用该本地脚本。 !1328

本轮版本与 CHANGELOG 约定

本 PR 基于主线 650b23155516aad28ef980f3388e41c74ca39b7a(1.2.578),统一升版一次至 1.2.579,同一 CHANGELOG 条目汇总业务修复并补记 !1441 漏定版内容。原主题3/6的拆分升版约定已被取代;后续修订不重复升版,目标基线推进时重新核对候选版本。独立片段与手工定版并存的要求见 版本流程,本次遗留片段的处理见 事故补救记录。

主题1、2、4、5、7仍会改变异常输入处理、数值解码或启停/销毁时序;此前拆分时没有单独升版,不等于只有测试变化。主题8、9为测试和辅助统计工具,主题10为独立本地构建入口,其变化不修改正式服务接口或正式构建 recipe。

EEPROM 多块读取:先检查实际回复长度再复制

对应 !1335,生产代码在 chip_eeprom::block_read,回归用例在 test_chip_eeprom.cpp。

通用 chip_base::data_access 现在把空读回复也传回调用方,避免预分配空间中的零值或旧数据 被误当作实际读取结果。EEPROM 多块循环原先只判断总线返回码,却没有检查每块回复的实际长度:

// 修改前:data_access 返回 HAS_OK 后,直接按请求长度拼接。
out_buffer.insert(out_buffer.end(), read_buffer.begin(), read_buffer.begin() + read_length);

// 修改后:在构造复制范围前拒绝空回复和非空短回复。
if (read_buffer.size() < read_length) {
    error = m_object_name + " read failed: short reply at offset=" + std::to_string(read_offset) +
            ", length=" + std::to_string(read_length) + ", received=" + std::to_string(read_buffer.size());
    return HAS_ERR;
}
out_buffer.insert(out_buffer.end(), read_buffer.begin(), read_buffer.begin() + read_length);

std::vector<uint8_t> 是 C++ 标准库的可变长字节数组;可以用 C 中的 struct { uint8_t *data; size_t size; size_t capacity; } 理解其存储信息。 size() 是有效字节数,不是分配容量。data_access 的 out_buffer = std::move(data.out_buffer) 把回复数组的存储所有权和有效长度交给调用方, 不是向原先固定大小的数组中复制;因此先前预分配了 32 字节,不代表返回后仍有 32 个有效字节。 这是 std 标准容器的移动语义,与项目的 mc 智能指针无关。

begin() 可类比指向首字节的 C 指针;begin() + read_length 是复制结束位置, insert 使用左闭右开的范围追加数据。若只收到 3 字节,却构造 4 字节范围,效果类似 让 C 的 memcpy 从有效长度为 3 的缓冲区读取 4 字节;容量恰好够大也不能使这个范围合法。 这属于未定义行为,外层 try/catch 不能保证接住。新增检查相当于 C 中先判断 received < requested 再返回错误,确保复制路径只接收足够长的回复。

失败时保留此前完整块,当前短块不追加,也不再读取后续块;调用方必须检查 HAS_ERR, 不能把已完成的前缀当作完整结果。错误文本包含当前块的偏移、请求长度和实际长度,便于定位设备回复。 本次防护针对多块拼接的固定长度复制;单块路径仍直接传回回复,由已有上层解析器检查其长度。

FirstMiddleAndLast/chip_eeprom_short_fragment.RejectsIncompleteReplyBeforeAppending 的六组参数 覆盖 100 字节请求中首块、中间块、末块的空回复,以及分别少 1 字节的回复: (offset, received) = (0,0), (0,31), (32,0), (32,31), (96,0), (96,3)。 测试沿真实 block_read → data_access 路径执行,仅以既有总线替身代替硬件;同时断言错误返回、 停止后续读请求、输出仅保留完整前缀和错误上下文。已有成功用例验证整块、末块和非对齐起点的正常数据。

2026-10-14 修复前验证中,末块短回复用例明确失败:实际返回 HAS_OK,并追加了不完整块; 首块空回复在 GDB 下停于 block_read → vector::_M_range_insert → memcpy 的 SIGSEGV。 调试器自身退出码为 0 不能作为测试通过依据。本地使用普通宿主构建,未加入 sanitizer 插桩; 修复后执行结果以对应提交的 PR 自验证报告及原始日志为准,不据此宣称已完成目标板或全仓 ASan 验证。

从 C 的资源清理顺序理解生命周期修改

以下片段用于解释上述 PR,不能理解为本文所在分支已经包含其他九项源码。

以 drivers/csr1/bus/common/interface/i_bus.cpp 为例,修改前后创建 timer 的关键代码是:

// 修改前:接口地址同时作为 timer 的父对象
m_access_timer = mc::make_shared<mc::timer>(this);

// 修改后:timer 由成员持有,执行器另行绑定
m_access_timer = mc::make_shared<mc::timer>();
auto* owner = get_owner();
m_access_timer->bind_executor(owner != nullptr ? owner->executor() : executor());

// 修改后:先执行析构函数体,再由 C++ 自动销毁成员
Interface_Bus::~Interface_Bus()
{
    stop_access_timer();
}

this 是当前接口地址,可类比 C 回调的 void *ctx。接口可能是 struct card 中按值内嵌的一个字段,timer 的父指针不能在这块存储结束后再使用。mc::make_shared<mc::timer>() 的尖括号指定创建的类型,返回由项目 mc::shared_ptr 管理的 timer 句柄;省略 this 切断的是父对象关系。auto* owner 仍是普通借用指针,只由编译器推导类型;条件表达式选择回调被投递到哪个执行器,不会自动保护所有成员并发访问。

回调中的 [this] 是 lambda 捕获列表,只保存接口地址,类似 C 的函数指针加 ctx;保存地址不会延长接口寿命。因此还必须在 ctx 失效前停止活动工作。~Interface_Bus() 是析构函数,会在对象被销毁时执行;C++ 在其函数体执行完以后才自动拆除该类的成员,故这里有机会先做相当于 C stop_and_wait(ctx) 的收尾。

主题5在更新协议对象和包编号前停止旧 timer,主题6在宿主其他成员销毁前停止遥测,都是对这一顺序的具体应用:

风险路径 A:旧回调仍用 ctx → ctx 的依赖被覆盖/销毁 → 回调继续读取失效状态
修复顺序 A:停止旧任务 → 等待已进入的业务回调退出 → 再更新或销毁依赖
风险路径 B:接口存储结束 → 外部仍持有的 timer 晚释放 → 沿旧父关系回访接口
修复路径 B:timer 创建时不建立该父关系 → 晚释放不再沿这条关系访问接口

图示是需要切断的路径,不表示本轮已重现所有历史崩溃。依据本轮核对的 libmcpp 10829fb9,stop() 取消旧代际并等待其他执行线程上已进入的业务回调;从同一 timer 的回调内停止会避开等待自身。它不保证所有排队闭包已执行,也不保证 timer 已销毁。成员 reset() 仅释放当前那份引用,执行器或其他持有者仍可能让 timer 继续存在。无父 timer、智能指针、绑定执行器都不能单独代替收尾,更不能保证任意兄弟成员销毁顺序安全。实际依赖包仍须核对,源码阅读不等于缓存二进制来源已经证明。

主题3在 drivers/csr1/chip/common/interface/i_aggregate.cpp::set_owner_object() 中进一步处理未托管对象:

// 原汇总实现
if (owner != nullptr) {
    m_owner_weak = owner->weak_from_this();
}
// 拆分修订后
if (owner != nullptr && owner->is_managed()) {
    m_owner_weak = owner->weak_from_this();
}

这里的 mc::weak_ptr 是项目封装,其弱计数位于宿主内部,不能套用标准库 std::weak_ptr 的内部实现。可把它理解成“借用 ctx,同时保存一项将来仍会访问 ctx 内计数的清理动作”:即使尝试取得强引用的 lock() 返回空,稍后释放这个弱引用仍可能需要访问计数。未托管栈对象离开作用域后,这种清理就不安全,因此先用 is_managed() 限制适用对象。运行时 lock() 成功获得的强引用只延长宿主存活时间,不提供互斥保护。

调度器的弱引用让空闲注册不再阻止宿主销毁;运行期间持有宿主则保护尚未完成的工作。相当于 C 中在提交异步操作前增加 ctx 引用计数,完成后再减回去。若操作永不完成,不能仅因超时就释放仍可能被访问的 ctx。wait_for_tasks() 还会跳过当前同步执行状态,避免等待自身;从当前回调内注销返回,并不代表当前回调已经结束。 允许异步注销后保留运行状态的前提是任务受托管且其寿命完全绑定宿主(lifetime_bound_to_owner()),不能推广为任意异步回调都可安全注销;可替换子任务和未托管任务从任务执行上下文之外注销时仍需等待。

主题3还处理宿主最终释放到注销任务之间的窗口。Scheduler::on_timer() 原来发现宿主弱引用已失效就跳过任务,却留下已到期的 next_run_ms;后面的 arm_next() 仍把它当成最早到期项,以最小间隔 10ms 反复唤醒。修改如下:

// 修改前
if (entry.run_state->has_owner && !owner) {
    continue;
}
// 修改后
if (entry.run_state->has_owner && !owner) {
    entry.next_run_ms = UINT64_MAX;
    continue;
}

可以把它理解为 C 事件循环中的 struct task { void *ctx; uint64_t deadline; }:跳过 callback(ctx) 并没有取消下次唤醒;将 deadline 设成这里已有的“不参与排期”标记,才停止为它重新设定 timer。这个分支不解引用宿主;条目保留到析构中的 unregister_task() 删除,其他任务仍照常按到期时间排期。

两个 dispatcher 的 owner_guard() 也改成调用时尝试取得强引用。以 NormalDispatcher::owner_guard() 为例,SmcDfxDispatcher 对应使用 owner_base:

// 修改前:nullptr / is_managed 检查之后
try {
    return m_owner_object->shared_from_this();
} catch (const std::exception&) {
    return {};
}
// 修改后:仍先保留 nullptr / is_managed 检查
return m_owner_object->weak_from_this().lock();

本项目 mc::shared_ptr 的裸指针构造函数带 noexcept,即不允许异常离开;shared_from_this() 会走这个构造函数,再调用增加强引用计数的 add_ref()。宿主最后一次释放把计数置为 0 后,add_ref() 会抛异常,越过 noexcept 时程序终止,外层 catch 接不到。is_managed() 只排除从未纳入引用计数管理的对象,计数已归零仍可能返回 true,因此它不能防住这个窗口。

新语句先产生临时的 mc::weak_ptr,不增加强引用,再由 lock() 原子地尝试增加强引用:成功返回可保活宿主的句柄,失败返回空;语句结束会释放临时弱引用。用 C 类比,就是从强制 retain(ctx) 改为允许返回 NULL 的 try_retain(ctx)。这些是项目 mc 的具体规则,不代表所有智能指针都有相同实现。

这里没有在 set_owner 时缓存弱引用:先设置裸 owner、以后才建立 managed 所有权仍然有效;未托管对象仍不借出引用,避免被错误收养。调用方仍须保证 owner 与 dispatcher 的存储有效,也仍须在正常初始化/启动期间注册、销毁前注销;弱升级不能验证任意悬空指针,不能授权在宿主析构中并发注册。对应回归通过对象外的同步信号暂停析构,保证 dispatcher 和基类尚未结束生命周期,仅查询 guard,不在析构中注册。

原调度路径:宿主计数归零 → 跳过任务但保留旧 deadline → 每 10ms 再次唤醒
修复调度路径:宿主计数归零 → deadline = UINT64_MAX → 等既有注销删除条目
原借引用路径(存储仍有效):计数已归零 → add_ref 抛出 → noexcept 触发终止
修复借引用路径(同一前提):计数已归零 → try_add_ref 失败 → 返回空 guard

主题6的 drivers/pcie_nvme/huawei/interface/upgrade.cpp 将等待升级线程提取为:

void NVMe_Upgrade::wait_for_completion()
{
    if (m_upgrade_thread && m_upgrade_thread->joinable()) {
        m_upgrade_thread->join();
    }
}

这与 C 中在释放线程使用的 ctx 之前调用 pthread_join() 的目的相同。joinable() 表示该线程句柄仍可连接,不等于线程已经结束;join() 可能等待。设备析构入口先调用这条收尾,再进入成员销毁;只依赖升级接口自身晚些时候析构,无法保护那些可能更早被销毁的兄弟成员。此处也不承诺任意执行上下文中的等待都不会阻塞。

本轮证据如何核对

  • 每项 PR 的测试结果必须标明实际执行的提交 SHA、构建配置及依赖;改动后若复用旧结果,写明原 SHA 和源码/文件等同性依据。README 不固定一组会随检视整改变化的“当前十项总通过数”。
  • 本轮 C++ 定向回归使用 Linux 宿主构建、启用断言,部分依赖复用既有 libmcpp 缓存。源码 checkout 10829fb9 不足以证明缓存二进制由该源码构建;未补齐来源时,不宣称重编译了最新依赖。
  • 定向回归通过、机器人 CI 的成功标签和原50轮记录是不同证据。未执行的目标板、完整产品、完整多轮压力或 sanitizer 插桩验证必须明确列出;历史 Samsung 第44轮间歇性 SIGSEGV 不能凭一次定向通过就宣称闭环。
  • DISABLED_ 未执行用例不计入通过数,跳过、禁用及过滤条件要分别披露。压力工具不对白名单跳过放行,也不以统计容差掩盖缺失用例。
  • 缓冲区/数值修复应核对正常值、边界值、短帧、错误长度和异常返回;生命周期修复应核对重复启停、在途销毁、外部保留 timer、最终释放和实际执行器配置。使用 mock 的范围与真实硬件/传输层异步路径要分开说明。

串行多轮 gtest 压力测试

scripts/run_memory_audit_stress.py 对一个已经构建完成的 Meson 测试目录逐轮串行执行,并保存每轮控制台输出、Meson JSON/TXT/XML 日志及 summary.json。脚本不负责构建,也不会因发现源码变化自动重编译。

在仓库根目录执行,下例的 builddir 应替换为本次已经配置并编译完成的测试目录:

ninja -C builddir
python3 scripts/test_run_memory_audit_stress.py
python3 scripts/run_memory_audit_stress.py \
  --build-dir builddir \
  --rounds 50 \
  --round-timeout 7200 \
  --log-root test_failure_logs/memory-audit

需要 Python 3、Meson,以及该构建所需的运行时依赖。--round-timeout 是整轮超时秒数,每个测试程序仍受 Meson 自身配置的超时限制。日志目录带 UTC 时间和进程号;首个失败轮次会停止后续轮次,脚本以非零状态退出。超时或中断时,脚本向本轮 Meson 进程组发送终止信号,必要时再强制终止。

计数与通过条件

  • 从 meson-info/intro-tests.json 读取当前配置的程序清单,拒绝缺失、重复或意外的程序结果。
  • Meson 结果必须全部为 OK;成功程序也必须至少执行一个 gtest 用例。
  • gtest 的失败、跳过和仅开始未结束的用例均导致整轮失败。后台日志与 gtest 标记交错时,统计器按待完成用例名称匹配结果,避免将日志交错误记为用例未完成。
  • 首个通过轮次确定实际执行用例数,后续轮次必须一致;全部请求轮次通过后,总结中的 passed 才为 true。

该计数器不主动启用 DISABLED_ 用例,也不会枚举全部 gtest 用例来校验环境中的过滤条件。多轮数量一致不能证明所有源码用例均已执行。提交测试报告时,应记录构建选项、过滤条件、实际执行数量、跳过和禁用范围。

构建与运行证据

运行前记录源码提交、构建选项和实际加载依赖。包装器会优先加入仓库中的 .libmcpp_prebuilt 库目录,并按测试名称调整 temp/usr/lib64、.lua_runtime_libs 与已有 LD_LIBRARY_PATH 的顺序;这些目录不存在时跳过。需先确认其库与当前构建匹配,不能只凭源码提交认定预编译库来自同一版本。

本脚本不创建私有 /tmp、共享内存、D-Bus 或 Linux 命名空间;有隔离需求时,应通过自己的 CI 或测试环境提供隔离。不要停止其他任务的 D-Bus 或清理其他构建目录。脚本启动的进程组之外的后台服务应由创建它的测试环境负责回收。

多轮通过只说明该构建和依赖下的测试结果,不等同于 ASan、UBSan 或其他内存检测通过。需在编译、链接阶段启用相应插桩,并保存完整诊断;仅设置 sanitizer 环境变量不会为现有二进制补上插桩。本工具不配置默认 sanitizer 抑制,也不随附覆盖整个外部依赖头文件的 vptr 抑制。外部依赖报错应保留原始证据并单独定位。

历史记录与回归范围

2026-09-27 的历史记录(报告提交 8bdd569f)记录了旧构建 50 轮通过,每轮 207 个测试程序、11,566 个 gtest 用例,总计 578,300 次用例运行。该构建未启用 sanitizer 插桩,且之后仍有源码和依赖变化;这些数字不作为当前分支或拆分 PR 的验证结果。历史机器上的私有隔离脚本和日志目录不属于可直接复用的仓库命令。

历史审计涉及协议回复和写入缓冲区边界、CSR1 调度及宿主引用、嵌入接口定时器的停止和销毁顺序、NVMe 升级线程收尾,以及 SMART 128 位计数转换。对应修复与回归由各自 PR 独立说明;提交报告应区分已运行的定向回归、新的多轮结果和仍未完成的验证。


构建与 tests 编译优化纪要

更新日期:2026-04-02 约束:--clean-all 保持三级清理不变(clean-prebuilt + 删除 libmcpp 源码缓存 + 清空 ccache)。下文「冷构建」指该口径下的全量重建。

冷构建全量验证(最新)

  • 命令:./scripts/smart_build.sh --clean-all
  • 结果:94/94 OK,Timeout: 0
  • 同机参考耗时:libmcpp 编译+缓存 约 2m48s,component_drivers 编译(Step4)约 17m39s,测试执行 约 48s,总计 约 21m16s
  • 瓶颈:Step4 仍以 tests 相关目标为主;根因多为 drivers 实现源码在 tests 中被重复编译,通过 Meson 复用 drivers 侧库/静态归档收敛

tests 侧主要已落地改动(按模块)

方向 要点
网卡 Mellanox test_mellanox_interface 等复用 libmellanox_interface_dep;CX4/CX5/CX6 的 card/port/om 测试改为 link_whole drivers 侧 libcx4_static / libcx5_static / libcx6_static,去掉 tests 内对卡型 .cpp 的二次静态库
网卡 Boardcom drivers 侧 dev_boardcom_interface + tests 链接 libboardcom_interface_dep;Emulex 多测例复用 emulex_test_static_lib 等(见对应 meson.build)
网卡 Hisi / Wangxun interface 测试改为依赖 drivers 已构建接口库(libhisi_interface_dep、libwangxun_interface_dep)
GPU Nvidia / Moorethreads / Vastai / Innosilicon:interface 与 drivers 共享库对齐,tests 不再直编 interface 源;rpath:usr/lib64 + opt/bmc/drivers
NVMe Huawei/Samsung 公共 *_test_impl_lib、test_nvme_mock_lib 等收敛重复源与子目录顺序
稳定性 test_scanner:stop() 后等待延长,避免悬空 service 指针与偶发 SIGSEGV(原 func_collection 清理已随 mcexpr 作用域重构移除,函数表改存对象 extras)
公共基础设施 tests/meson.build:test_main_lib、test_chip_interface_lib、各类 mock 静态库等,减少跨目标重复编译

热缓存稳态(ccache 高命中,与冷构建口径不同)

以下数据来自历史多轮 --clean-all 在 ccache 已预热 下的统计,不可与上表「清空 ccache」冷构建直接对比。

指标 基准(约第 1 轮) 较好水平 说明
总 wall-clock ~2060s 102~130s 区间 依赖机器负载与 I/O
Step4 主编译 ~1859s 40~66s 区间 ccache 命中时以链接/归档为主
Ninja 目标数 1751 约 1429 含 test_main 等收敛
main.cpp 编译次数 92 1 test_main_lib

建议

  • 日常开发优先 --clean 或增量构建,缩短迭代;发布/CI 全量仍用 --clean-all
  • 未采用默认 release + b_ndebug 等激进编译选项(曾导致多测例 SIGSEGV,已回退)
  • 进一步缩短冷构建:继续按 .ninja_log 找「tests 重复编 drivers 源」的路径,对齐 drivers 库 + tests 依赖 + rpath 模式

编译问题修复记录

本次共修复三类编译问题,涉及 50+ 个测试文件和 47 个驱动 meson.build 文件。修复后全量编译零失败。


问题一:接口重构后测试代码未同步更新

1.1 现象

编译报大量 has no member named 错误,例如:

'class dev::PCIeDevice' has no member named 'SetDeviceBDF'
'class dev::PCIeDevice' has no member named 'DevBus'
'class dev::NetworkAdapter' has no member named 'HandleOsResetSignal'
'NetworkAdapter_Cooling' is not a member of 'dev'
'class dev::Gpu' has no member named 'TemperatureCelsius'
'class dev::PCIeCard' has no member named 'Model'
'bool dev::PCIeDevice::SetDeviceBDF(...)' marked 'override', but does not override

涉及 pcie_nic_card(hisi/mellanox/wangxun/boardcom)、pcie_gpu_card(nvidia/moorethreads/vastai/innosilicon)、pcie_nvme(huawei/samsung)三大子系统的几乎全部测试。

1.2 原因

源代码头文件进行了大规模接口拆分重构——将原本集中在基类上的属性和方法拆分到了语义更明确的子类中。但测试代码仍引用旧的基类成员名,导致编译失败。

设计原理:此次重构遵循单一职责原则,将一个"大而全"的基类拆分为多个功能聚焦的子接口。例如 PCIeDevice 原来同时承载"根端口BDF"和"Function级别BDF"两套属性,拆分后根端口属性留在 PCIeDevice(Bus/Device/Function),Function 级属性移到新的 PCIeDevice_PCIeFunction(BusNumber/DeviceNumber/FunctionNumber)。

1.3 完整的 API 迁移映射表

PCIe 相关(全子系统通用)
旧 API(基类) 新 API(子类) 变更类型
PCIeDevice::SetDeviceBDF() PCIeDevice_PCIeFunction::SetDeviceBDF() 方法搬移
PCIeDevice::DevBus PCIeDevice_PCIeFunction::BusNumber 搬移+改名
PCIeDevice::DevDevice PCIeDevice_PCIeFunction::DeviceNumber 搬移+改名
PCIeDevice::DevFunction PCIeDevice_PCIeFunction::FunctionNumber 搬移+改名
PCIeDevice::VendorId / DeviceId PCIeDevice_PCIeFunction::VendorId / DeviceId 仅搬移
PCIeDevice::SubSystemVendorId / SubSystemDeviceId PCIeDevice_PCIeFunction::SubSystemVendorId / SubSystemDeviceId 仅搬移
PCIeDevice::pcie_device_bdf_changed PCIeDevice_PCIeFunction::pcie_device_bdf_changed 信号搬移
PCIeDevice::start_ncsi_update_task_*() PCIeDevice_PCIeFunction::start_ncsi_update_task_*() 方法搬移
PCIeDevice::update_pcie_device_id_by_ncsi() PCIeDevice_PCIeFunction::update_pcie_device_id_by_ncsi() 方法搬移
PCIeDevice::stop_ncsi_update_task() PCIeDevice_PCIeFunction::stop_ncsi_update_task() 方法搬移
PCIeCard::Model / Manufacturer / PartNumber / SerialNumber PCIeDevice::Model / Manufacturer / PartNumber / SerialNumber 从 PCIeCard 搬到 PCIeDevice
PCIeCard::read_serialnumber() 等 PCIeDevice::read_serialnumber() 等 读取方法同步搬移
网卡相关(pcie_nic_card)
旧 API 新 API 变更类型
NetworkAdapter::register_os_reset_callback() NetworkAdapter_Oem::register_os_reset_callback() 方法搬移
NetworkAdapter::unregister_os_reset_callback() NetworkAdapter_Oem::unregister_os_reset_callback() 方法搬移
NetworkAdapter::HandleOsResetSignal() NetworkAdapter_Oem::HandleOsResetSignal() 方法搬移
dev::NetworkAdapter_Cooling(类名) dev::Cooling(类名) 类重命名
card->m_network_adapter_cooling card->m_cooling 卡成员重命名
GPU 卡相关(pcie_gpu_card)
旧 API 新 API 变更类型
Gpu::TemperatureCelsius Processor_Metrics::TemperatureCelsius 属性搬移
Gpu::read_mtt_gpu_temperature() / read_temp() Processor_Metrics::read_mtt_gpu_temperature() / read_temp() 方法搬移
Gpu_Power::PowerWatts Processor_Metrics::PowerWatts 属性搬移
Gpu_Power::read_gpu_power_consumption() / read_power() Processor_Metrics::read_gpu_power_consumption() / read_power() 方法搬移
Memory::Utilization Memory_Metrics::MemoryUsagePercent 搬移+改名
dev::smbus_ptr / dev::postbox_ptr 各厂商 processor/metrics.h / pcie_device.h 中定义 typedef 位置变更

1.4 修改方案

A. 测试文件修改(约 50 个文件)

针对接口级测试(直接创建接口对象进行测试的场景):

// 修改前:直接在 PCIeDevice 上调用
m_device->SetDeviceBDF(0x19, 0x03, 0x04);
EXPECT_EQ(m_device->DevBus.get_value().as<uint8_t>(), 0x19);

// 修改后:新建 PCIeDevice_PCIeFunction 对象
// 1. 在 fixture 的 SetUp 中创建: m_pcie_function = mc::make_shared<dev::PCIeDevice_PCIeFunction>();
// 2. 通过新对象调用:
m_pcie_function->SetDeviceBDF(0x19, 0x03, 0x04);
EXPECT_EQ(m_pcie_function->BusNumber.get_value().as<uint8_t>(), 0x19);

针对卡级测试(通过 card 对象访问成员的场景):

// 修改前
device->m_pcie_device.SetDeviceBDF(0x18, 0x02, 0x03);
device->m_pcie_device.DevBus = 0;
device->m_network_adapter.HandleOsResetSignal();
device->m_network_adapter_cooling.stop_ncsi_update_task();

// 修改后:改用正确的子对象成员
device->m_pcie_device_pcie_function.SetDeviceBDF(0x18, 0x02, 0x03);
device->m_pcie_device_pcie_function.BusNumber = 0;
device->m_network_adapter_oem.HandleOsResetSignal();
device->m_cooling.stop_ncsi_update_task();

B. NVMe 驱动源码修改(非测试代码)

本次修复涉及 pcie_nvme 子系统的 正式驱动代码(非测试),以下逐一说明修改原因和合理性:

B1. huawei/samsung 的 interface/pcie_device.h —— 移除 SetDeviceBDF 声明

// 修改前
class PCIeDevice : public mc::engine::interface<PCIeDevice, gen::PCIeDevice> {
    bool SetDeviceBDF(uint8_t, uint8_t, uint8_t) override;       // ← 编译报错
    mc::signal<void(uint8_t, uint8_t, uint8_t)> pcie_device_bdf_changed;  // ← 已搬走
};

// 修改后
class PCIeDevice : public mc::engine::interface<PCIeDevice, gen::PCIeDevice> {
    bool SetRootBDF(uint8_t, uint8_t, uint8_t) override;         // 保留
    void replace_dynamic_property(...);                           // 保留
};
  • 原因:gen::PCIeDevice 基类中已移除 SetDeviceBDF 虚函数(拆分到 gen::PCIeDevice_PCIeFunction),override 关键字找不到基类虚函数,编译必然报错 marked 'override', but does not override。
  • 合理性:SetDeviceBDF 设置的是 Function 级 BDF(VendorId 所在的设备端点),而 SetRootBDF 设置的是根端口 BDF(物理插槽位置)。NVMe 卡类(hw_nvme_pcie4)仅使用根端口 BDF 来初始化 MCTP 通信,不需要 Function 级 BDF,因此移除正确。

B2. huawei/samsung 的 interface/pcie_device.cpp —— 移除 SetDeviceBDF 实现

// 移除的代码(使用了已不存在的属性名)
bool PCIeDevice::SetDeviceBDF(uint8_t bus, uint8_t device, uint8_t function) {
    DevBus      = bus;       // ← gen::PCIeDevice 上已不存在
    DevDevice   = device;    // ← 同上
    DevFunction = function;  // ← 同上
    ...
}
  • 原因:实现中引用的 DevBus/DevDevice/DevFunction 属性已从 gen::PCIeDevice 移除(迁移到 gen::PCIeDevice_PCIeFunction 并更名为 BusNumber/DeviceNumber/FunctionNumber)。
  • 合理性:此函数的头文件声明已移除,实现自然也需要移除。

B3. hw_nvme_pcie4.cpp —— 属性名从 DevBus 改为 Bus

// 修改前(15 处)
if ((m_pcie_device.DevBus == 0) && (m_pcie_device.DevDevice == 0) && (m_pcie_device.DevFunction == 0))
uint16_t phy_addr = mctp::init_phy_addr(m_pcie_device.DevBus, m_pcie_device.DevDevice, m_pcie_device.DevFunction);

// 修改后
if ((m_pcie_device.Bus == 0) && (m_pcie_device.Device == 0) && (m_pcie_device.Function == 0))
uint16_t phy_addr = mctp::init_phy_addr(m_pcie_device.Bus, m_pcie_device.Device, m_pcie_device.Function);
  • 原因:gen::PCIeDevice 基类中根端口 BDF 的属性名从 DevBus/DevDevice/DevFunction 重命名为 Bus/Device/Function。
  • 合理性:NVMe 卡使用根端口 BDF 来计算 MCTP 物理地址,此处访问的确实是根端口属性,改用 Bus/Device/Function 语义正确。

B4. hw_nvme_pcie4.cpp —— 移除 pcie_device_bdf_changed 信号连接

// 修改前
if (!start_mctp_protocol()) {
    m_pcie_device.pcie_device_bdf_changed.connect([this](...) {
        start_mctp_over_pcie();
    });
}

// 修改后
if (!start_mctp_protocol()) {
    // TODO: pcie_device_bdf_changed 已移到 PCIeDevice_PCIeFunction
    wlog("MCTP protocol start failed, BDF change callback not registered");
}
  • 原因:pcie_device_bdf_changed 信号已从 PCIeDevice 搬到 PCIeDevice_PCIeFunction,而 hw_nvme_pcie4 的 MC_OBJECT 宏中未注册 PCIeDevice_PCIeFunction 接口,卡类没有 m_pcie_device_pcie_function 成员,无法访问此信号。
  • 合理性:这是临时措施。完整修复需要在 hw_nvme_pcie4.h 的 MC_OBJECT 中添加 (PCIeDevice_PCIeFunction) 接口并在 MC_REFLECT 中注册成员,属于更大范围的架构变更,记为 TODO。

B5. huawei/samsung 的 interface/pcie_device/pcie_function.h/cpp —— 新建文件

drivers/pcie_nvme/huawei/interface/pcie_device/pcie_function.h   (新建)
drivers/pcie_nvme/huawei/interface/pcie_device/pcie_function.cpp (新建)
drivers/pcie_nvme/samsung/interface/pcie_device/pcie_function.h  (新建)
drivers/pcie_nvme/samsung/interface/pcie_device/pcie_function.cpp(新建)
  • 原因:NVMe 接口级测试(test_pcie_device.cpp)需要测试 SetDeviceBDF 功能,该方法现在属于 PCIeDevice_PCIeFunction 类,必须有对应的驱动实现才能编译链接。
  • 合理性:文件结构和实现逻辑与 hisi/mellanox 的参考实现保持一致(都继承 gen::PCIeDevice_PCIeFunction,实现 SetDeviceBDF override,发射 pcie_device_bdf_changed 信号)。信号触发使用 post() 异步方式,与 hisi 参考实现一致。
  • 注意事项:当前这些文件仅被测试编译使用,NVMe 卡类(hw_nvme_pcie4)尚未注册 PCIeDevice_PCIeFunction 接口。待后续卡类添加该接口后,这些文件将成为正式驱动代码的一部分。

1.5 涉及的测试文件清单

子系统 涉及文件
hisi test_pcie_device.cpp, test_network_adapter.cpp, test_hi182x_card.cpp, test_hi1822_fc_card.cpp
mellanox test_cx{4,5,6}_{card,port,om}.cpp, test_pcie_device.cpp, test_network_adapter.cpp, test_network_adapter_cooling.cpp
wangxun test_pcie_device.cpp, test_network_adapter.cpp, test_network_adapter_cooling.cpp, test_pcie_card.cpp, test_rp1000_card.cpp, test_sfx00ht_card.cpp
boardcom test_pcie_device.cpp, test_network_adapter.cpp, test_network_adapter_cooling.cpp, test_pcie_card.cpp, test_emulex_card.cpp
pcie_nvme huawei/interface/pcie_device.{h,cpp}(驱动源码), samsung/interface/pcie_device.{h,cpp}(驱动源码), hw_nvme_pcie4.cpp(驱动源码), test_pcie_device.cpp
pcie_gpu_card nvidia/moorethreads/vastai/innosilicon 的 test_*_card.cpp, test_*_gpu.cpp, test_*_memory.cpp, test_pcie_card*.cpp, test_gpu_read_*.cpp, test_interface.cpp

问题二:测试 meson.build 缺少新增源文件(链接失败)

2.1 现象

编译阶段通过,但链接阶段报大量 undefined reference 错误:

undefined reference to `vtable for dev::PCIeDevice_PCIeFunction'
undefined reference to `dev::PCIeDevice_PCIeFunction::stop_ncsi_update_task()'
undefined reference to `mc::reflect::reflector<dev::NetworkPort_Oem, void>::get_metadata()'
undefined reference to `dev::Processor_Metrics::read_mtt_gpu_temperature(...)'

2.2 原因

接口拆分时新建了子类的 .cpp 实现文件(如 pcie_device/pcie_function.cpp、pcie_card/oem.cpp 等),但测试的 meson.build 中只列出了旧有的源文件,未包含新增文件。

原理:本项目的测试采用源码级编译方式——将被测驱动的 .cpp 源文件直接列入测试可执行文件的 sources 列表,而非链接预编译的共享库。因此新增的 .cpp 必须手动添加到 meson.build 中。

2.3 修改方案

在各子系统测试的 meson.build 中,向 *_interface_sources 变量添加缺失文件:

pcie_nic_card(hisi / mellanox / boardcom / wangxun),每个厂商添加 6 个文件:

'../../../../../drivers/pcie_nic_card/XXX/interface/pcie_device/pcie_function.cpp',
'../../../../../drivers/pcie_nic_card/XXX/interface/pcie_device/oem.cpp',
'../../../../../drivers/pcie_nic_card/XXX/interface/pcie_device/status.cpp',
'../../../../../drivers/pcie_nic_card/XXX/interface/pcie_card/oem.cpp',
'../../../../../drivers/pcie_nic_card/XXX/interface/network_port/oem.cpp',
'../../../../../drivers/pcie_nic_card/XXX/interface/optical_module/oem.cpp',

mellanox 额外还需 pcie_device/bandwidth.cpp 和 pcie_device/hotswap.cpp。

pcie_gpu_card(nvidia / moorethreads / vastai / innosilicon),每个厂商添加 6 个文件:

'../../../../../drivers/pcie_gpu_card/XXX/interface/processor.cpp',
'../../../../../drivers/pcie_gpu_card/XXX/interface/processor/metrics.cpp',
'../../../../../drivers/pcie_gpu_card/XXX/interface/memory/metrics.cpp',
'../../../../../drivers/pcie_gpu_card/XXX/interface/pcie_device/pcie_function.cpp',
'../../../../../drivers/pcie_gpu_card/XXX/interface/pcie_device/oem.cpp',
'../../../../../drivers/pcie_gpu_card/XXX/interface/pcie_device/status.cpp',
'../../../../../drivers/pcie_gpu_card/XXX/interface/pcie_card/oem.cpp',

pcie_nvme(huawei / samsung),每个厂商添加 1 个文件:

'../../../../../drivers/pcie_nvme/XXX/interface/pcie_device/pcie_function.cpp',

2.4 涉及的 meson.build 文件

共修改约 20 个 meson.build 文件,分布在 tests/drivers/pcie_nic_card/、tests/drivers/pcie_gpu_card/、tests/drivers/pcie_nvme/ 下各厂商的 interface/ 和具体型号目录中。


问题三:共享库复制命令报 "are the same file"

3.1 现象

编译时所有 47 个驱动库的复制步骤均失败:

FAILED: drivers/internal/copy_libInternal
/usr/bin/cp: '.../libInternal.so' and '.../opt/bmc/drivers/libInternal.so' are the same file

导致后续依赖这些库的测试无法链接。

3.2 原因

各驱动的 meson.build 使用 custom_target + cp 命令将编译产物 .so 复制到统一的 opt/bmc/drivers/ 目录。在增量构建场景下,如果前一次构建的 cp 在底层文件系统上创建了硬链接(某些文件系统/内核版本的默认行为),则源文件和目标文件已是同一个 inode,cp 会检测到并拒绝执行。

原理:Linux cp 命令默认会检测源和目标是否为同一文件(通过 stat 比较 st_dev + st_ino),如果相同则报错退出。--remove-destination 参数会在复制前先 unlink 目标文件,从而断开硬链接关系。

3.3 修改方案

在 drivers/ 下全部 47 个 meson.build 文件中,给 cp 添加 --remove-destination 参数:

# 修改前
command: ['cp', lib.full_path(), drivers_install_dir]

# 修改后
command: ['cp', '--remove-destination', lib.full_path(), drivers_install_dir]

使用 find + sed 一条命令批量完成:

find drivers -name 'meson.build' -exec grep -l "command: \['cp'," {} \; \
  | xargs sed -i "s/command: \['cp',/command: ['cp', '--remove-destination',/g"

3.4 涉及的文件

drivers/ 下 47 个 meson.build,覆盖 internal、bus/*、chip/*、accessor、scanner、pcie_nic_card/*、pcie_gpu_card/*、pcie_nvme/*、fan、psu、jbog、connector、debounce/* 等全部驱动模块。


运行时测试失败修复记录

编译修复后运行 meson test -C builddir 共 91 个测试,初始 70 个失败。经过十一轮修复,全部 91 个通过。

阶段 通过 失败 本轮修复数
初始状态 21 70 —
问题四:securec 空桩 81 10 +60
问题五~七:GPU .so 路径 / Mellanox 测试 / Broadcom 空指针 85 6 +4
问题八~十:Hi182x / Wangxun 设备数 / Wangxun CSR 88 2+1超时 +3
问题十一~十四:NVMe mutex / NV L40 超时 / Hisi 10 用例 / Hi1822FC TearDown 91 0 +3+1超时

问题四:securec 安全函数空桩导致全面崩溃(修复 60 个测试)

4.1 现象

70 个失败测试中 61 个在第一个用例即崩溃(SIGSEGV/SIGABRT),另 9 个测试断言失败(stod 异常、固件版本为空等)。

D-Bus 崩溃的典型错误信息:

dbus[PID]: arguments to dbus_message_iter_open_container() were incorrect,
assertion "... contained_signature_validity == DBUS_VALID" failed

PMBus/CAN 总线的典型错误:

C++ exception with description "stod" thrown in the test body.

4.2 原因

subprojects/libmcpp/stub/securec.h 中所有安全函数(strncpy_s、memcpy_s、sprintf_s、snprintf_s 等)均为空操作——参数直接丢弃、返回成功:

static inline errno_t strncpy_s(char* strDest, size_t destMax, const char* strSrc, size_t count) {
    (void)strDest; (void)destMax; (void)strSrc; (void)count;
    return EOK;  // 假装成功,实际什么都没做
}

component_drivers/stub/securec.h 已有正确的打桩实现(委托标准库函数),但 meson.build 中只用 add_project_arguments('-I...') 设置了本项目的搜索路径。Meson 的 add_project_arguments 不会作用于子项目,因此 libmcpp 编译时仍使用自己的空桩。

影响链路 A — D-Bus 签名损坏(61 个崩溃):libmcpp/src/dbus/message.cpp 中 message_writer 构造函数用 strncpy_s 将 D-Bus 类型签名拷入栈缓冲区 sig_buf,空桩什么都没拷贝,缓冲区全是栈垃圾。dbus_message_iter_open_container 收到垃圾签名后触发 libdbus 内部断言或内存越界。

影响链路 B — 格式化输出静默失败(9 个断言错误):mc::fmt::format_v 内部使用 snprintf_s / vsnprintf_s,空桩返回 0 且不写入任何字符。std::stod("") 抛出 invalid_argument(PMBus 6 个测试);format_v("DC:%02x PFC:%02x", ...) 返回空字符串导致固件版本比较失败(CAN 总线 3 个测试)。

4.3 修改方案

不修改 subprojects/libmcpp/stub/securec.h(子项目文件不应修改),而是利用编译器 -include 和头文件守卫实现替换:

文件 1:meson.build — 将 enable_conan_compile 判断和 securec 配置提前到 libmcpp_dep 声明之前(Meson 要求 add_global_arguments 在任何构建目标之前调用);添加 add_global_arguments:

if not enable_conan_compile
  stubdir_global = include_directories('stub')
  add_project_arguments('-I' + meson.current_source_dir() / 'stub', language: 'cpp')
  add_global_arguments(
    '-include', meson.current_source_dir() / 'stub' / 'securec.h',
    language: 'cpp',
  )
endif

# ↓ libmcpp_dep 必须在 add_global_arguments 之后
libmcpp_dep = dependency('libmcpp', fallback: ['libmcpp', 'libmcpp_dep'], ...)

文件 2:stub/securec.h — strncpy_s 改为逐字符循环拷贝(兼容 string_view::data() 非 null 结尾场景):

static inline errno_t strncpy_s(char* strDest, size_t destMax, const char* strSrc, size_t count) {
    if (strDest == NULL || strSrc == NULL || destMax == 0) return EINVAL;
    size_t copy_len = 0;
    while (copy_len < count && copy_len < destMax - 1 && strSrc[copy_len] != '\0') {
        strDest[copy_len] = strSrc[copy_len];
        copy_len++;
    }
    strDest[copy_len] = '\0';
    return EOK;
}

4.4 原理

-include 是 GCC/Clang 编译器选项,在编译任何源文件之前强制包含指定头文件,等效于在源文件第一行插入 #include "securec.h"。利用 #ifndef SECUREC_H 头文件守卫实现"先到先得":

编译器处理 libmcpp 源文件时:
1. -include component_drivers/stub/securec.h  → 正确实现被包含,SECUREC_H 宏被定义
2. #include "securec.h"                        → 找到 libmcpp/stub/securec.h(空桩)
3. #ifndef SECUREC_H                           → 已定义,整个空桩文件被跳过

add_global_arguments 与 add_project_arguments 的区别:前者作用于所有目标(含子项目),后者仅作用于当前项目。

4.5 涉及文件

文件 修改说明
meson.build enable_conan_compile + securec 配置块提前到 libmcpp_dep 之前;新增 add_global_arguments('-include', .../securec.h')
stub/securec.h strncpy_s 从 strlen + memcpy 改为逐字符循环拷贝

问题五:GPU 卡 ABI 测试缺少共享库搜索路径(修复 2 个测试)

5.1 现象

test_nv_common_abi 和 test_vg1000_abi 的 SetUpTestSuite 失败,全部用例被 SKIP:

C++ exception with description "Failed to load driver: libdev_nvidia_interface.so:
cannot open shared object file: No such file or directory" thrown in SetUpTestSuite().

5.2 原因

ABI 测试在 SetUpTestSuite 中通过 dlopen("./opt/bmc/drivers/libnv_l40.so") 加载驱动共享库。加载成功后,动态链接器还需要解析该 .so 的依赖(如 libdev_nvidia_interface.so)。这些依赖 .so 已构建并复制到 builddir/opt/bmc/drivers/,但该目录未在测试的 LD_LIBRARY_PATH 中。

对比 Mellanox cx4 等通过的 ABI 测试,它们在 meson.build 的 test() 调用中显式设置了 env: ['LD_LIBRARY_PATH=.../opt/bmc/drivers:...']。nv_l40 和 vg1000 的 test() 调用缺少该配置。

5.3 修改方案

在 test() 调用中添加 env 参数:

test('test_nv_common_abi', test_nv_common_abi,
     workdir: meson.build_root(),
     timeout: 180,
     env: ['LD_LIBRARY_PATH=' + meson.project_build_root() + '/opt/bmc/drivers:'
           + meson.project_build_root() + '/gen:'
           + meson.project_build_root() + '/subprojects/libmcpp/src:'
           + meson.project_build_root() + '/libraries/protocol:'
           + meson.project_build_root() + '/libraries/smbus'])

5.4 原理

meson test 运行时 CWD 为 builddir/,dlopen("./opt/bmc/drivers/libnv_l40.so") 通过相对路径找到主 .so。但 libnv_l40.so 动态链接了 libdev_nvidia_interface.so(NEEDED 条目),加载器按 LD_LIBRARY_PATH → /etc/ld.so.conf → 默认路径的顺序搜索依赖。如果 LD_LIBRARY_PATH 不含 opt/bmc/drivers/,依赖解析失败。

5.5 涉及文件

文件 修改说明
tests/drivers/pcie_gpu_card/nvidia/nv_l40/meson.build test() 添加 env 含 LD_LIBRARY_PATH
tests/drivers/pcie_gpu_card/vastai/vg1000/meson.build 同上

问题六:Mellanox 链路状态测试期望值与实现不一致(修复 1 个测试)

6.1 现象

test_mellanox_interface 中 NetworkPortLinkInfoTest.UpdateLinkStatusByNcsi_GetLinkStatusFailed 断言失败:

Expected equality of these values:
  m_link_info->SpeedMbps.get_value()
    Which is: 4294967295
  0

6.2 原因

mellanox/interface/network_port/link_info.cpp 在 NCSI 获取链路状态失败时将 SpeedMbps 设为 4294967295(UINT32_MAX),表示"无效/未知速度"。但测试期望值为 0,与实现逻辑不一致。

6.3 修改方案

修改测试期望值以匹配实现:

// 修改前
EXPECT_EQ(m_link_info->SpeedMbps.get_value(), 0);

// 修改后
EXPECT_EQ(m_link_info->SpeedMbps.get_value(), 4294967295U);

6.4 原理

NCSI 规范中,链路状态获取失败时各字段应设为无效标识值。UINT32_MAX (0xFFFFFFFF) 是通用的无效标记,与 LinkStatus=255 和 FullDuplex=255 保持语义一致(都是最大值表示未知)。0 可能被误解为"0 Mbps",语义不如 UINT32_MAX 明确。

6.5 涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/mellanox/interface/network_port/test_link_info.cpp SpeedMbps 期望值 0 → 4294967295U

问题七:Broadcom 网卡 PLDM 空指针崩溃(修复 1 个测试)

7.1 现象

test_boardcom_interface 中 UpdateFirmwareVersionByPldm_PldmNotInitialized 触发 SIGSEGV:

[  RUN  ] BoardcomNetworkAdapterTest.UpdateFirmwareVersionByPldm_PldmNotInitialized
killed by signal 11 SIGSEGV

7.2 原因

boardcom/interface/network_adapter.cpp 中 update_firmware_version_by_pldm() 第一行直接解引用 m_pldm_over_mctp_emulex 指针:

bool NetworkAdapter::update_firmware_version_by_pldm() {
    emulex_fru_info_t fruinfo = {};
    bool ret = m_pldm_over_mctp_emulex->get_emulex_fru_info(&fruinfo);  // ← 空指针

测试故意通过 start_pldm_update_task_emulex(nullptr, interval) 传入空指针,验证实现是否能安全处理"PLDM 未初始化"的场景。但实现缺少空指针保护。

7.3 修改方案

在函数开头添加空指针检查:

bool NetworkAdapter::update_firmware_version_by_pldm() {
    if (!m_pldm_over_mctp_emulex) {
        ilog("Error: PLDM over MCTP not initialized");
        return false;
    }
    emulex_fru_info_t fruinfo = {};
    bool ret = m_pldm_over_mctp_emulex->get_emulex_fru_info(&fruinfo);
    ...
}

7.4 原理

防御式编程:对外部传入的智能指针/原始指针,在使用前检查有效性。start_pldm_update_task_emulex() 将参数存入 m_pldm_over_mctp_emulex,后续定时器回调会调用 update_firmware_version_by_pldm(),此时如果传入的是 nullptr,解引用即段错误。添加空指针检查使函数安全返回 false,由调用者决定是否重试。

7.5 涉及文件

文件 修改说明
drivers/pcie_nic_card/boardcom/interface/network_adapter.cpp update_firmware_version_by_pldm() 开头添加 m_pldm_over_mctp_emulex 空检查

问题八:Hi182x 光模块测试断言与函数职责不匹配(修复 1 个测试)

8.1 现象

test_hi182x_abi 中 test_optical_module_info_parsing_with_trailing_spaces 断言失败:

Expected equality of these values:
  om_device->m_optical_module.Presence.get_value()
    Which is: 0
  1

217/218 个用例通过,仅此 1 个失败。

8.2 原因

该测试调用 get_optical_module_info_by_ncsi() 后断言 Presence == 1。但 get_optical_module_info_by_ncsi() 的职责是解析光模块型号/厂商/序列号等信息字段,不负责设置 Presence。Presence 由独立的 get_presence_info_by_ncsi() 函数设置。

测试断言超出了被测函数的职责范围。

8.3 修改方案

移除该测试中对 Presence 的断言:

// 修改前
EXPECT_EQ(om_device->m_optical_module.Presence.get_value(), 1);
EXPECT_EQ(om_device->m_optical_module.Manufacturer.get_value(), manufacturer_with_spaces);

// 修改后(删除 Presence 断言,保留其余)
// Presence 由 get_presence_info_by_ncsi() 设置,不在 get_optical_module_info_by_ncsi() 职责内
EXPECT_EQ(om_device->m_optical_module.Manufacturer.get_value(), manufacturer_with_spaces);

8.4 原理

单元测试应遵循单一职责:只验证被测函数声明要做的事情。Presence 属性的测试已在其他用例(如 test_optical_module_presence_*)中覆盖,此处删除不影响覆盖率。保留一个与被测函数无关的断言反而会在 Presence 获取方式变更时引起误报。

8.5 涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/hisi/hi182x/test_hi182x_card.cpp 移除 test_optical_module_info_parsing_with_trailing_spaces 中的 Presence 断言

问题九:Wangxun rp1000 驱动注册设备数量变更(修复 1 个测试中的 1 个用例)

9.1 现象

test_rp1000_abi 中 register_device_driver_success 断言失败:

Expected equality of these values:
  m_devices.size()
    Which is: 3
  2

9.2 原因

rp1000_abi.cpp 中 rp1000_device_driver[] 数组现包含 3 个设备类型(PCIeNicCard、NicPort、OpticalTransceiver),新增了光模块设备。但测试仍断言 m_devices.size() == 2,未同步更新。

9.3 修改方案

更新断言值并增加对新设备类型的验证:

// 修改前
EXPECT_EQ(m_devices.size(), 2);
EXPECT_TRUE(m_devices.find("PCIeNicCard") != m_devices.end());
EXPECT_TRUE(m_devices.find("NicPort") != m_devices.end());

// 修改后
EXPECT_EQ(m_devices.size(), 3);
EXPECT_TRUE(m_devices.find("PCIeNicCard") != m_devices.end());
EXPECT_TRUE(m_devices.find("NicPort") != m_devices.end());
EXPECT_TRUE(m_devices.find("OpticalTransceiver") != m_devices.end());

9.4 原理

ABI 兼容性测试应反映驱动实际导出的设备列表。register_device_driver 函数返回的 count 由 sizeof(rp1000_device_driver) / sizeof(rp1000_device_driver[0]) 计算,当数组新增 OpticalTransceiver 条目后,返回值从 2 变为 3。测试应同步更新。

9.5 涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/wangxun/rp1000/test_rp1000_abi.cpp 设备数量期望 2 → 3,新增 OpticalTransceiver 断言

问题十:Wangxun 测试 CSR 反序列化异常(修复 2 个测试中的共 14 个用例)

10.1 现象

test_rp1000_abi 和 test_sfx00ht_abi 中多个 NCSI/协议相关用例抛出异常:

C++ exception with description "类型错误:期望object,实际为null" thrown in the test body.

涉及用例:StartNcsiProtocol_SP1000AModel、StartNcsiProtocol_OtherModelWithBdf、StartProtocol_RegistersBdfCallback、StartNcsiProtocol_MultipleCalls 等。

10.2 原因

测试通过 csr["bmc.dev.PCIeDevice"]["Model"] = "SP1000A" 在 CSR 字典中创建了一个只含 Model 字段的 bmc.dev.PCIeDevice 子字典。rp1000_card::init() 调用 from_variant(csr_object, *this) 时,框架检测到 bmc.dev.PCIeDevice 键存在,便尝试反序列化整个 PCIeDevice 接口的所有属性。缺失的属性(如引用类型字段)返回 null variant,as<object>() 转换时抛出类型异常。

对比其他通过的测试(如 test_rp1000_card_start_stop_basic),它们的 CSR 只含 {"SystemId", 1},不包含 bmc.dev.PCIeDevice 键,from_variant 不会触碰 PCIeDevice 接口的属性。

10.3 修改方案

不在 CSR 字典中设置 bmc.dev.PCIeDevice 子键,改为 init() 之后直接在 device 对象上赋值:

// 修改前:通过 CSR 字典(触发 from_variant 对整个接口反序列化)
auto csr = create_test_csr();
csr["bmc.dev.PCIeDevice"]["Model"] = std::string("SP1000A");
device_driver->init(device, &csr, &connector);

// 修改后:init 后直接设置属性(跳过 from_variant)
auto csr = create_test_csr();
device_driver->init(device, &csr, &connector);
device->m_pcie_device.Model = std::string("SP1000A");

10.4 原理

from_variant 是 mc::engine 框架的序列化机制,它将字典中的键映射到对象的接口属性上。当字典中存在某个接口的键(如 bmc.dev.PCIeDevice)时,框架会认为调用者有意配置该接口,并尝试加载所有属性(含引用类型、对象类型等)。如果字典只提供了部分字段,缺失字段的 null 值在类型转换时会抛出异常。

直接赋值 device->m_pcie_device.Model = ... 绕过了 from_variant 的全接口反序列化,只修改单一属性,不触碰其他字段。

10.5 涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/wangxun/rp1000/test_rp1000_card.cpp 7 处 csr["bmc.dev.PCIeDevice"]["Model"] → device->m_pcie_device.Model
tests/drivers/pcie_nic_card/wangxun/sfx00ht/test_sfx00ht_card.cpp 7 处同上

问题十一:NVMe 接口测试 boost mutex 崩溃(修复 1 个测试)

11.1 现象

test_nvme_interface 中 PCIeDeviceTest.ReplaceDynamicProperty_SlotWithoutPlaceholder 触发 SIGABRT:

boost::signals2::mutex::lock(): Assertion `pthread_mutex_lock(&m_) == 0' failed.

11.2 原因

测试 fixture 的 SetUp/TearDown 每个用例都创建/销毁 mc::make_shared<dev::PCIeDevice>()。反复构造/析构 mc::engine 接口对象导致 D-Bus 信号系统的 boost mutex 累积损坏。另外 SetDeviceBDF_SignalEmitted 测试在静态对象上连接信号但未断开,连接跨用例累积。

11.3 修改方案

  1. 将 m_pcie_device 和 m_pcie_function 从每用例创建改为 suite 级静态对象(只创建/销毁一次)
  2. SetDeviceBDF_SignalEmitted 测试改为 atomic 变量接收异步信号,加 sleep(100ms) 等待 post() 投递,用例结束显式 disconnect()

11.4 原理

mc::make_shared 创建的引擎对象会向 D-Bus 注册属性和信号。频繁创建/销毁导致 libdbus 内部的 mutex 引用计数紊乱。将对象提升为 suite 级避免了反复注册/注销。post() 是异步投递,测试需等待事件循环处理完毕才能验证信号是否到达。

11.5 涉及文件

文件 修改说明
tests/drivers/pcie_nvme/huawei/interface/test_pcie_device.cpp m_pcie_device/m_pcie_function 改为 static + SetUpTestSuite 创建;SignalEmitted 用 atomic + sleep + disconnect

问题十二:NV L40 ABI 测试超时(修复 1 个测试)

12.1 现象

test_nv_common_abi 运行 180s 后被 SIGTERM 杀死。

12.2 原因

nv_common_gpu::init() 中 init_smbus_postbox() 失败后有一个 do-while 重试循环,每次重试 sleep_for(15s),共 5 次 = 75s。测试中 RefChip 未注册导致 init_smbus_postbox() 永远失败。多个测试用例(stop_gpu_with_inactive_timer、init_gpu_without_ref_chip 等)都调用 init(),累计超过 180s。

12.3 修改方案

将重试间隔从 15s 缩短为 3s(3s * 5 = 15s),同时将测试超时从 180s 提高到 300s。

12.4 原理

15s 的重试间隔是为生产环境中等待 SMBus 设备就绪设计的,对于测试来说过于保守。3s 在生产环境中仍然足够(硬件初始化通常在 1-2s 内完成),而测试中多个用例累计不超过 60s。

12.5 涉及文件

文件 修改说明
drivers/pcie_gpu_card/nvidia/nv_l40/nv_common_gpu.cpp sleep_for(15s) → sleep_for(3s)
tests/drivers/pcie_gpu_card/nvidia/nv_l40/meson.build timeout: 180 → timeout: 300

问题十三:Hisi 接口测试多类失败(修复 10 个用例)

13.1 现象

test_hisi_interface 共 262 个用例,10 个失败。

13.2 原因与修改(按子类别)

A. SMBus mock 分派失败(修复 6 个 link_status + 1 个 presence 用例)

std_smbus::send_request() 不是 virtual 方法,mock 子类的重写被 C++ 名称隐藏。通过基类指针调用时执行的是真实 std_smbus::send_request,真实实现做 CRC 校验 → 校验失败 → 返回空。

修改:在 libraries/smbus/std_smbus.h 中将 send_request 声明为 virtual。

B. NCSI SpeedMbps 期望值(修复 1 个用例)

同问题六。实现在 NCSI 获取失败时设 SpeedMbps=UINT32_MAX,测试期望 0。

修改:测试期望值 0U → 4294967295U。

C. FaultState 存储值语义(修复 2 个用例)

实现中 FaultState.set_value(health_state) 存储的是健康状态码(0=健康,非0=不健康),不是第一个错误码。测试期望 FaultState 为错误码(0x1001/0x2001)。另外 FaultCode 的分隔符实现用 ", "(逗号+空格),测试期望 ","(仅逗号+尾逗号)。

修改:测试 mock 数据 health_state 从 0 改为 1(进入错误码解析分支);FaultState 期望值改为 1;FaultCode 期望字符串对齐实现格式。

D. 光模块 presence opcode 不匹配(修复 1 个用例)

测试 mock 数据设置 opcode 0x0417,但实现中 update_presence_info_by_smbus 请求的是 0x0414。

修改:测试 opcode 0x0417 → 0x0414。

E. 零 MAC 地址覆盖有效值(修复 1 个用例,含产品代码修复)

实现中 update_permanent_mac_addr_by_smbus 无条件设置 PermanentMACAddress,全零 MAC 也会覆盖已有有效值。

修改:在实现中添加零 MAC 检查,跳过全零地址。

13.3 涉及文件

文件 修改说明
libraries/smbus/std_smbus.h send_request 添加 virtual 关键字
tests/.../hisi/interface/test_network_port.cpp SpeedMbps 期望值 0U → 4294967295U
tests/.../hisi/interface/test_network_adapter.cpp FaultState 期望值、FaultCode 格式对齐实现
tests/.../hisi/interface/test_optical_module.cpp presence opcode 0x0417 → 0x0414
drivers/.../hisi/interface/network_port.cpp 零 MAC 地址检查:跳过 "00:00:00:00:00:00"

问题十四:Hi1822FC 测试 TearDown 定时器未停止(修复 1 个测试)

14.1 现象

test_hi1822_fc 所有 84 个用例通过,但进程在 TearDown 阶段 SIGSEGV(仅在 MALLOC_PERTURB_ 启用时复现)。

14.2 原因

Hi1822FcOpticalModuleTest::TearDown() 中 m_om.reset() 销毁对象,但未显式停止 SMBus 定时器。MALLOC_PERTURB_ 将释放的内存填充为垃圾值,定时器回调访问已释放对象时触发段错误。不启用 MALLOC_PERTURB_ 时,内存内容未被覆盖,碰巧不崩溃。

14.3 修改方案

在 TearDown 中显式停止 SMBus 定时器,并等待异步回调完成后再销毁对象:

void TearDown() override {
    if (m_om) {
        m_om->m_optical_module_temperature_celsius.stop_smbus_update_task();
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
        m_om.reset();
    }
}

14.4 原理

meson test 设置 MALLOC_PERTURB_=N 环境变量,glibc 的 malloc/free 会用 N ^ 0xFF 填充分配的内存、用 N 填充释放的内存。这使得 use-after-free 类 bug 从"偶发不崩溃"变为"必现崩溃"。显式停止定时器确保回调函数在对象销毁前已经完成。

14.5 涉及文件

文件 修改说明
tests/.../hisi/hi1822_fc/test_hi1822_fc_om.cpp TearDown 中添加 stop_smbus_update_task() + sleep

问题十五:MCTP 异步回调 use-after-free —— m_destroying 成员标志的根本性缺陷

15.1 发现经过

通过 mctp_class_test 中的 try_get_endpoint_object_retry_count_boundary 用例发现。该用例调用 try_get_endpoint_object(59, cb) 触发 DBus 异步请求和 1 秒定时器重试链,然后在 2.5 秒后销毁 mctp 对象。在 MALLOC_PERTURB_ 启用(meson test 默认行为)的条件下,对象销毁后内存被填充为垃圾值,异步回调中通过 this 指针访问 m_destroying 成员时概率性触发 SIGSEGV 或读到垃圾值继续执行后崩溃。

该问题同样影响 create_transport_and_endpoint_normal、create_transport_and_endpoint_empty_module_name 等所有涉及异步回调 + 对象销毁的用例。

15.2 旧代码(提交 13e54dd)

提交 13e54dd("支持smbus获取MAC地址和带宽速率信息")为了防止定时器回调访问已删除的对象,引入了 std::atomic<bool> m_destroying 成员标志:

// mctp.h
class mctp : public mc::object {
    ...
    std::atomic<bool> m_destroying = false;
};

// mctp.cpp 析构函数
mctp::~mctp() {
    m_destroying = true;  // 设置标志
    ...
}

// 异步回调中检查
.then([this](mc::dbus::message reply) {
    if (m_destroying.load()) {   // 通过 this->m_destroying 检查
        return mc::dict();
    }
    ...
})

15.3 为何这个方案有根本性缺陷

m_destroying 是对象自身的成员变量,其生命周期与对象绑定。当析构函数执行完毕后,对象的全部内存(包括 m_destroying)都被释放。此时如果异步回调被调度执行:

  1. this 指针已悬垂(dangling pointer)
  2. this->m_destroying 访问的是已释放的内存
  3. 这是未定义行为(Undefined Behavior)

用一句话总结:你不能通过一个已死的对象的成员来判断这个对象是否已死。

15.4 为何是概率性的

这个 bug 表现为概率性崩溃,原因有三:

(1)竞态时序不确定

try_get_endpoint_object 发起 DBus 异步请求后,回调何时被调度取决于事件循环的状态。如果回调在对象析构之前执行完毕,则不会触发问题。只有当回调在析构之后才被调度时,才会访问已释放内存。这取决于:

  • 系统负载和线程调度
  • DBus 请求的超时时长(1 秒)
  • 定时器的触发精度

(2)内存复用的不确定性

在不启用 MALLOC_PERTURB_ 的正常运行中,对象被 free() 后内存内容可能暂时不变。此时 this->m_destroying 碰巧读到析构函数写入的 true 值,回调"正确"地提前返回——bug 被掩盖。但这只是运气好,如果内存被复用(另一个 malloc 获得了同一块内存),读到的就是垃圾值。

(3)MALLOC_PERTURB_ 放大效应

meson test 默认设置 MALLOC_PERTURB_=N 环境变量,glibc 会用 N 填充释放的内存。这使得 this->m_destroying 必定读到垃圾值而非 true,回调不会提前返回,继续通过 this 访问其他已释放成员 → 必现崩溃。这就是为什么该 bug 在本地运行时偶尔通过,但在 meson test 下必现失败。

15.5 修复方案:shared_ptr<atomic<bool>> 存活标志

将存活标志的生命周期从对象本身解耦出来:

// mctp.h —— 新方案
class mctp : public mc::object {
    ...
    std::shared_ptr<std::atomic<bool>> m_alive;
};

// mctp.cpp 构造函数
mctp::mctp(...) : ..., m_alive(std::make_shared<std::atomic<bool>>(true)) { ... }

// mctp.cpp 析构函数
mctp::~mctp() {
    m_alive->store(false);   // 标记失效,但 shared_ptr 指向的内存由引用计数管理,不会被释放
    ...
}

// 异步回调通过值捕获 shared_ptr 副本
.then([this, alive](mc::dbus::message reply) {
    if (!alive->load()) {    // 通过自己持有的副本访问,不依赖 this
        return mc::dict();
    }
    ...  // 确认存活后才安全地使用 this
})

15.6 原理

核心思想是共享所有权 + 引用计数保证内存可达:

  1. std::make_shared<std::atomic<bool>>(true) 在堆上分配一个独立于 mctp 对象的 atomic<bool>
  2. mctp 对象持有一份 shared_ptr,每个 lambda 通过值捕获持有自己的 shared_ptr 副本
  3. 当 mctp 析构时,m_alive->store(false) 标记失效,然后 m_alive 被销毁(引用计数减一)。但 lambda 中的副本仍然持有引用,所以 atomic<bool> 的内存不会被释放
  4. 回调执行时,通过自己的 alive 副本检查标志:如果为 false,直接返回,不再通过 this 访问任何成员
  5. 当最后一个 lambda 执行完毕或被销毁,最后一份 shared_ptr 析构,atomic<bool> 的内存才被释放

时序对比:

旧方案(m_destroying 成员):
  对象析构 → m_destroying 随对象一起被释放 → 回调执行 → this->m_destroying 访问已释放内存 → UB

新方案(shared_ptr<atomic<bool>> m_alive):
  对象析构 → m_alive->store(false),对象内存被释放
           → 回调执行 → alive 副本仍有效(引用计数>0)→ 读到 false → 安全退出,不碰 this

本质上这是 C++ 异步编程中的弱引用守卫模式(类似 weak_ptr 的作用),在回调执行时先通过独立于对象生命周期的标志检查目标是否存活,再决定是否继续操作。

15.7 涉及文件

文件 修改说明
libraries/mctp/mctp.h std::atomic<bool> m_destroying → std::shared_ptr<std::atomic<bool>> m_alive;新增 #include <memory>
libraries/mctp/mctp.cpp 构造函数初始化 m_alive;析构函数 m_alive->store(false);所有异步 lambda 值捕获 alive 副本并替代 this->m_destroying 检查

15.8 关联问题

本问题与问题十四(Hi1822FC TearDown 定时器 use-after-free)属于同一类缺陷——异步回调访问已销毁对象。问题十四通过在测试中显式停止定时器来规避,而本问题从产品代码架构层面彻底解决,使异步回调即使在对象销毁后执行也不会触发未定义行为。


问题十六:Emulex 网卡 init_network_ports 使用 static_cast 导致类型错误崩溃

16.1 现象

test_emulex_card 的 102 个用例中 InitNetworkPorts_WithNonPortObject 在 TearDown 阶段触发 SIGSEGV:

[ RUN      ] EmulexCardTest.InitNetworkPorts_WithNonPortObject
emulex_card init network port NicPort_other_1
emulex_card () stop
emulex_card () stop
Segmentation fault (core dumped)

该用例创建了一个名字以 NicPort 开头、但实际类型为 emulex_card(而非 emulex_port)的子对象,用于验证 init_network_ports 是否能安全过滤非端口对象。

16.2 原因

emulex_card::init_network_ports() 通过对象名前缀判断是否为网络端口,然后使用 static_cast 做向下转型:

for (auto& object : objects) {
    auto object_name = object->get_name();
    if (object_name.substr(0, object_name.find_first_of("_")) == "NicPort") {
        m_network_ports.push_back(static_cast<emulex_port*>(object.get()));  // 危险!
    }
}

当子对象名字匹配 NicPort 前缀但实际类型不是 emulex_port 时,static_cast 仍然"成功"——它直接做指针偏移,生成一个指向错误内存布局的 emulex_port* 指针。这个无效指针被存入 m_network_ports。

后续在 stop() → stop_pldm_update_task() 中遍历 m_network_ports 调用 port->stop_pldm_protocol() 时,通过 vtable 查找虚函数地址。由于指针指向的实际对象是 emulex_card(内存布局完全不同),vtable 偏移指向了无效内存 → SIGSEGV。

16.3 背后原理:static_cast vs dynamic_cast

static_cast<Derived*>(base_ptr):

  • 编译期操作,不做任何运行时检查
  • 编译器根据类继承关系计算指针偏移量,直接调整指针值
  • 如果实际类型不是 Derived 或其子类,结果是未定义行为
  • 优点:零运行时开销;缺点:类型安全完全由程序员保证

dynamic_cast<Derived*>(base_ptr):

  • 运行时操作,依赖 RTTI(Run-Time Type Information)
  • 检查 base_ptr 指向的对象的实际类型(通过 vtable 中的 type_info)
  • 如果实际类型是 Derived 或其子类,返回正确偏移后的指针
  • 如果类型不匹配,返回 nullptr(指针版本)或抛出 std::bad_cast(引用版本)
  • 缺点:有少量运行时开销(通常为一次指针比较 + 类型链遍历)

在本场景中,子对象列表来自 get_children(),返回的是基类指针(mc::engine::abstract_object*)。仅通过对象名前缀无法保证类型正确,必须用 dynamic_cast 做运行时类型校验。

16.4 修改方案

for (auto& object : objects) {
    auto object_name = object->get_name();
    if (object_name.substr(0, object_name.find_first_of("_")) == "NicPort") {
        auto* port = dynamic_cast<emulex_port*>(object.get());
        if (port == nullptr) {
            elog("emulex_card child ${name} is not emulex_port, skip", ("name", object_name));
            continue;
        }
        elog("emulex_card init network port ${name}", ("name", object_name));
        m_network_ports.push_back(port);
    }
}

16.5 涉及文件

文件 修改说明
drivers/pcie_nic_card/boardcom/emulex/emulex_card.cpp init_network_ports() 中 static_cast<emulex_port*> → dynamic_cast<emulex_port*> + nullptr 检查

问题十七:所有 ABI 测试因 .so 未部署到 opt/bmc/drivers/ 而全部失败(修复 23 个测试)

17.1 现象

./scripts/setup.sh 配置后直接运行 meson test -C builddir,全部 23 个 _abi 测试失败(exit status 1),其余 68 个测试正常通过:

 6/91 component_drivers:i2c / test_i2c_abi           FAIL   0.05s   exit status 1
 7/91 component_drivers:hisport / test_hisport_abi    FAIL   0.05s   exit status 1
23/91 component_drivers:smc / test_smc_abi            FAIL   0.03s   exit status 1
69/91 component_drivers:accessor / test_accessor_abi  FAIL   0.06s   exit status 1
...

Ok: 68    Fail: 23

测试日志中所有失败都是同一个错误:

C++ exception with description "Failed to load driver: ./opt/bmc/drivers/libI2c.so:
cannot open shared object file: No such file or directory" thrown in SetUpTestSuite().

builddir/opt/bmc/drivers/ 目录存在但为空,42 个 .so 文件均未复制。

17.2 原因

根因是 meson test 不构建 build_by_default: true 的 custom_target。 问题涉及两层缺陷:

(1)Meson 1.3.2 的 meson test 只构建测试依赖,不构建 all 目标

meson test 内部通过 ninja meson-test-prereq 构建,该目标仅包含测试可执行文件及其编译/链接依赖:

# build.ninja 中的两个目标
build meson-test-prereq: phony tests/test_i2c_abi tests/test_smc_abi ...  # 只有二进制
build all: phony ... drivers/bus/i2c/copy_libI2c ...                      # copy 在这里

各驱动 meson.build 中的 custom_target('copy_libI2c', ..., build_by_default: true) 属于 all 目标,但不在 meson-test-prereq 的依赖链上,因此 meson test 不会触发 .so 复制。

(2)custom_target 未创建 ninja 追踪的 sentinel 文件

即使通过 meson compile(等同于 ninja all)触发了复制,cp 命令只把 .so 复制到 opt/bmc/drivers/,不创建 ninja 追踪的输出文件(如 copy_libI2c)。后续增量构建中 ninja 的状态不一致——认为已完成但 .so 可能丢失。

# 旧代码:cp 不创建 sentinel 文件
custom_target(
    'copy_libI2c',
    output: 'copy_libI2c',             # ninja 期望的输出文件
    command: ['cp', '--remove-destination', i2c_lib.full_path(), drivers_install_dir],  # 只 cp,不 touch
    depends: i2c_lib,
    build_by_default: true,
)

17.3 修改方案(三层修复)

A. 根因修复:让 ABI 测试声明对 copy 目标的运行时依赖(28 个测试 meson.build)

Meson 的 test() 函数支持 depends 参数声明运行时依赖。将 copy 目标加入 depends 后,meson test 构建 meson-test-prereq 时会自动触发 .so 复制。

# 1. 顶层 meson.build:定义全局收集列表
all_copy_targets = []

# 2. 各驱动 meson.build:追加 custom_target 结果
all_copy_targets += custom_target(
    'copy_libI2c',
    output: 'copy_libI2c',
    command: ['sh', '-c', 'cp --remove-destination $1 $2 && touch $3', '--',
              i2c_lib.full_path(), drivers_install_dir, '@OUTPUT@'],
    depends: i2c_lib,
    build_by_default: true,
)

# 3. 各 ABI 测试 meson.build:声明运行时依赖
test('test_i2c_abi', i2c_test, timeout: 300, suite: 'i2c',
    depends: all_copy_targets)

B. sentinel 文件修复:cp 后 touch @OUTPUT@(59 个 driver/lib/gen meson.build)

将 command: ['cp', ...] 改为 command: ['sh', '-c', 'cp ... && touch $3', ...],确保 ninja 正确追踪构建状态:

# 修改前
command: ['cp', '--remove-destination', i2c_lib.full_path(), drivers_install_dir],

# 修改后
command: ['sh', '-c', 'cp --remove-destination $1 $2 && touch $3', '--',
          i2c_lib.full_path(), drivers_install_dir, '@OUTPUT@'],

C. 脚本层兜底:smart_build.sh 和 build.sh 添加 .so 部署验证函数

编译后自动扫描 .so 数量,若发现缺失则从编译产物目录补齐。应对极端边界场景(如手动删除 .so 但 sentinel 仍在):

verify_so_deployment() {
    local expected=$(find "$BUILD_DIR/drivers" -maxdepth 5 -name "*.so" \
        -not -path "*.so.p/*" -not -path "*/tests/*" | wc -l)
    local actual=$(ls -1 "$DRIVERS_DIR"/*.so 2>/dev/null | wc -l)
    if [ "$actual" -lt "$expected" ]; then
        echo "[WARN] opt/bmc/drivers/ 缺少 .so(${actual}/${expected}),执行兜底部署..."
        find "$BUILD_DIR/drivers" ... -exec cp {} "$DRIVERS_DIR/" \;
    fi
}

17.4 原理

meson test 的构建行为在 Meson 0.56+ 发生了变化:不再构建 all 目标,而是只构建 meson-test-prereq(测试可执行文件及其依赖)。build_by_default: true 的 custom_target 虽然属于 all,但如果不在任何测试的 depends 链上,meson test 不会触发它。

test() 的 depends 参数正是为此设计——声明测试运行时(而非编译时)需要的构建产物。添加后,Meson 会将这些目标加入 meson-test-prereq,使 meson test 自动构建。

touch @OUTPUT@ 使 ninja 能正确追踪 sentinel 文件的存在性:存在则跳过,删除则重新执行 cp+touch。

17.5 涉及文件

类别 文件 修改说明
顶层构建 meson.build 新增 all_copy_targets = [] 列表
驱动 .so 复制 drivers/ 下 47 个 meson.build cp → sh -c 'cp ... && touch @OUTPUT@';追加 all_copy_targets +=
库 .so 复制 libraries/ 下 11 个 + gen/meson.build 同上
ABI 测试依赖 tests/drivers/ 下 28 个 meson.build test() 添加 depends: all_copy_targets
构建脚本 scripts/smart_build.sh 新增 verify_so_deployment() 兜底函数
构建脚本 scripts/setup.sh 生成的 build.sh 内嵌 .so 部署验证
文档 README.md 常见问题新增 "ABI 测试全部失败" 条目

问题十八:CI 流水线 bingo test 找不到测试可执行文件(修复 54 个 meson.build)

18.1 现象

CI 环境中 bingo test -ut -jit 执行时,bingo 工具通过 meson install 收集测试可执行文件到安装目录,然后在安装目录中运行测试。但所有测试可执行文件均未被安装,bingo 报"分析单元测试结果失败"。

18.2 原因

测试 meson.build 中 executable() 的 install 参数为 false(或未设置,默认 false):

i2c_test = executable(
    'test_i2c_abi',
    i2c_test_sources,
    ...
    install: false,    # ← meson install 时不会安装此二进制
)

meson install 只安装 install: true 的目标。bingo test 依赖 meson install 将测试二进制部署到统一目录后运行,install: false 导致测试二进制不会出现在安装目录中。

本地 meson test -C builddir 不受影响(它直接从 builddir/ 运行测试,不依赖 install),因此本地测试全部通过但 CI 失败。

18.3 修改方案

将所有测试 executable() 的 install 设为 true:

# 修改前
i2c_test = executable(
    'test_i2c_abi',
    ...
    install: false,
)

# 修改后
i2c_test = executable(
    'test_i2c_abi',
    ...
    install: true,
)

对于未显式设置 install 的测试(默认 false),添加 install: true。

18.4 涉及文件

共 54 个测试 meson.build,其中 28 个与问题十七的 depends: all_copy_targets 变更重叠:

目录 文件数 说明
tests/drivers/bus/ 4 i2c, i2c_mux, hisport
tests/drivers/chip/ 11 ads78, complex, eeprom, ina, interface/chip, lm75, pca9545, pca9555, powermeter, smc
tests/drivers/accessor/ 1
tests/drivers/scanner/ 1
tests/drivers/connector/ 1
tests/drivers/debounce/ 4 average, binary_continue, continue, median
tests/drivers/internal/ 1
tests/drivers/psu/ 1
tests/drivers/pcie_nic_card/ 12 hisi(3), mellanox(4), wangxun(3), boardcom(2)
tests/drivers/pcie_gpu_card/ 8 nvidia(2), moorethreads(2), vastai(2), innosilicon(2)
tests/drivers/pcie_nvme/ 4 huawei(2), samsung(2)
tests/libraries/ 7 imu, ipmb, lldp, ncsi_over_mctp, nvme, pldm, pldm_over_mctp, protocol

本次修改总文件统计

全部 118 个文件,两个问题合计:

变更类别 文件数 修改内容
顶层 meson.build 1 新增 all_copy_targets = []
驱动 drivers/ 下 meson.build 47 cp → sh -c 'cp && touch';all_copy_targets +=
库 libraries/ + gen/ 下 meson.build 12 同上
测试 tests/ 下 meson.build(两类变更重叠) 28 install: true + depends: all_copy_targets
测试 tests/ 下 meson.build(仅 install) 26 install: true
构建脚本 2 smart_build.sh 兜底验证 + scripts/setup.sh 生成的 build.sh 验证
文档 2 README.md 常见问题 + tests/README.md 问题记录
合计 118

遗留事项

事项 位置 说明 修复建议
pcie_device_bdf_changed 信号未恢复 drivers/pcie_nvme/huawei/hw_nvme_pcie4/hw_nvme_pcie4.cpp:61 原代码在 MCTP 启动失败时监听 BDF 变化信号以自动重试,该信号已移到 PCIeDevice_PCIeFunction 1. 在 hw_nvme_pcie4.h 的 MC_OBJECT 宏中添加 (PCIeDevice_PCIeFunction) 接口;2. 添加 PCIeDevice_PCIeFunction m_pcie_device_pcie_function 成员;3. 在 MC_REFLECT 中注册 (m_pcie_device_pcie_function, "bmc.dev.PCIeDevice.PCIeFunction");4. 将信号连接改为 m_pcie_device_pcie_function.pcie_device_bdf_changed.connect(...)
NVMe pcie_function 文件仅被测试使用 drivers/pcie_nvme/{huawei,samsung}/interface/pcie_device/pcie_function.{h,cpp} 这些文件是为测试 SetDeviceBDF 而新建的,NVMe 卡类尚未注册 PCIeDevice_PCIeFunction 接口 待上一条遗留完成后,这些文件将自然成为正式驱动的一部分

文件统计

  • 总文件数:约 97 个 cpp 文件
  • 已有测试文件:约 50 个
  • 需要补充测试:约 47 个文件

处理策略

1. 优先级分类

高优先级(核心功能)

  • drivers/internal/ 目录下的所有文件
  • libraries/ 目录下的核心库文件
  • drivers/pcie_nic_card/hisi/interface/ 下的子文件

中优先级(接口文件)

  • drivers/*/interface/ 目录下的文件
  • drivers/chip/powermeter/ 目录下的文件

低优先级(已有部分测试)

  • 已有测试文件但覆盖率不足的文件

2. 测试用例编写指南

覆盖所有分支

  • 成功路径
  • 失败路径
  • 边界条件
  • 异常处理
  • nullptr 参数
  • 无效参数
  • 空数据/空集合

使用 Mock 对象

  • 对于依赖外部系统的代码,使用 Mock 对象
  • 参考 tests/drivers/accessor/test_accessor.cpp 中的 mock_chip_interface 示例

测试命名规范

  • 使用描述性名称:test_method_name_scenario
  • 例如:test_bitio_read_full_mask, test_init_accessor_null_params

3. 验证覆盖率

# 运行所有测试
meson test -C builddir

# 生成覆盖率报告
./scripts/coverage.sh

# 查看HTML报告
firefox coverage/html/index.html

覆盖率提升记录

基于覆盖率分析报告(tests/覆盖率分析和优化方案.md),识别出低覆盖率模块并针对性补充测试,目标使行覆盖率大于90%。


修复1:test_pmbus 测试用例修正(修复 8 个失败用例)

涉及文件

文件 修改说明
tests/libraries/pmbus/test_pmbus.cpp 修正 8 个测试用例的预期值,适配实现行为

问题

8 个测试用例因实现与测试预期不一致而失败:

测试用例 问题原因 修复方案
get_status_other_success 实现固定返回 0(TODO 注释),测试期望 mock 值 修改测试适配实现行为
get_status_other_no_response 同上,实现永远返回有值 修改测试验证固定返回 0
check_input_loss_bit_set 测试传入 0x08(bit3),实现检查 bit4(0x10) 改为传 0x10
write_read_event_log_no_response_returns_false chip_bytewrite 写操作在 mock 下返回 true 改为验证不崩溃
get_line_input_voltage_type_ac_wide/dc_neg48v/hvdc_2/no_response 测试 mock INPUT_TYPE,实现读取 POWER_TYPE 改为 mock POWER_TYPE

结果

test_pmbus 从 Fail(8 个失败)→ 全部 125 个用例通过


修复2:canbus 基类 canbus_get_factory_type 未实现

涉及文件

文件 修改说明
libraries/canbus/canbus.cpp 为基类添加 canbus_get_factory_type() 默认实现(返回 {false, -1})

问题

canbus.h 第 266 行声明了 canbus_get_factory_type(),但 canbus.cpp 中没有实现,导致 test_canbus 链接失败(undefined reference)。

结果

test_canbus 从链接失败 → 编译通过,测试全部通过


新增1:wangxun/interface/optical_module 测试(1.8% → ~70%,35 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/wangxun/interface/test_optical_module.cpp 新建,287 行
tests/drivers/pcie_nic_card/wangxun/interface/meson.build 加入 optical_module 源文件和测试文件
tests/drivers/pcie_nic_card/wangxun/README.md 记录覆盖率提升

新增用例(35 个)

  • 构造/析构测试(3 个)
  • port_id 设置/获取测试(4 个,含边界值)
  • 属性访问测试(15 个):ChannelNum、Presence、MediumType、Manufacturer、PartNumber、SerialNumber、TransceiverType、ConnectorType、TransmissionDistance、Accessible
  • stop_ncsi_update_task 安全性测试(2 个)
  • 多实例独立性测试(3 个)
  • 综合属性测试(2 个)
  • ncsi 测试(6 个):get_optical_module_info_by_ncsi、start_ncsi_update_task_wx,通过 mock MCTP 请求覆盖成功/失败/media_type 路径
  • OpticalModule_Oem 测试(6 个):fixture WangxunOpticalModuleOemTest,覆盖 UpdatePropWithoutBma、SetSubInterfaces、SetOpticalModuleOem_Null

新增2:mellanox/interface/optical_module 测试扩充(11.9% → ~70%,30+ 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/mellanox/interface/test_optical_module.cpp 重写,270 行

新增用例(30+ 个)

  • 构造/析构/非空验证(3 个)
  • 属性访问测试(20 个):覆盖所有公开属性的读写及边界值
  • 多实例独立性测试(2 个)
  • 综合属性测试(2 个)
  • OpticalModule_Oem 测试(6 个):fixture MellanoxOpticalModuleOemTest

新增3:samsung/interface/pcie_device 测试扩充(7.1% → ~80%,26 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nvme/samsung/interface/test_pcie_device.cpp 重写,266 行
tests/drivers/pcie_nvme/samsung/interface/README.md 新建

新增用例(26 个)

  • SetRootBDF 测试(10 个):成功/失败/边界值/属性更新验证
  • replace_dynamic_property 测试(12 个):各种占位符替换场景、异常场景
  • 多实例测试(2 个)

新增4:nvme_mi_vpd 测试扩充(50.1% → ~55%,12 个用例)

涉及文件

文件 修改说明
tests/libraries/nvme/test_nvme_mi_vpd.cpp 从 10 个扩充至 22 个用例

新增用例(12 个)

  • get_manufacturer_id_WithoutInit:未初始化时返回 false
  • get_capacity_mib_WithoutInit:未初始化时返回 false
  • get_capable_speed_gbs_WithoutInit:未初始化时返回 false
  • get_firmware_version_WithoutInit:未初始化时返回 false
  • get_firmware_version_CustomOffset_WithoutInit:自定义 offset 时未初始化返回 false
  • get_temperature_NullPointer_repeated:多次传空指针不崩溃
  • get_basic_management_info_NullPointer_repeated:多次传空指针不崩溃
  • AllGetters_WithoutInit_ReturnFalse:综合验证所有公开 getter 未初始化均返回 false

新增5:wangxun/interface/network_port 测试扩充(40.7% → ~55%,12 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/wangxun/interface/test_network_port.cpp 从 7 个扩充至 19 个用例

新增用例(12 个)

  • StopNcsiUpdateTask_WithoutStart:未 start 时 stop 不崩溃
  • StopNcsiUpdateTask_MultipleTimes:多次 stop 不崩溃
  • StartNcsiUpdateTask_DifferentPackageIds:不同 package_id 启动
  • StartNcsiUpdateTask_RestartAfterStop:stop 后重新 start
  • SendNcsiTestPacket_EmptyData:空数据未初始化返回 false
  • MultipleInstances_IndependentPortId:多实例 PortId 独立
  • UpdateMacAddr_AllFunctionsReturnFalse_NoNcsi:所有 NCSI 函数未初始化均返回 false
  • UpdateMacAddrByNcsi_MockFail:mock 失败时返回 false
  • GetLldpCapabilityByNcsi_MockFail:mock 失败时返回 false
  • GetLldpCapabilityByNcsi_Disabled:lldp_capability=0 时返回 true,LLDPEnabled=false
  • GetLldpCapabilityByNcsi_Enabled:lldp_capability=1 时返回 true,LLDPEnabled=true

新增6:mellanox/interface/network_adapter 测试扩充(31.7% → ~45%,11 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/mellanox/interface/test_network_adapter.cpp 从 17 个扩充至 28 个用例

新增用例(11 个)

  • UpdateFirmwareVersionByNcsi_NotInitialized:未初始化返回 false
  • UpdateFirmwareVersionByNcsi_FailedResponse:NCSI 失败返回 false
  • UpdateFirmwareVersionByNcsi_ZeroVersion:全零版本不崩溃
  • StartNcsiUpdateTask_DifferentPackageIds:不同 package_id 启动
  • StopNcsiUpdateTask_MultipleTimes:多次 stop 不崩溃
  • StartNcsiUpdateTask_NullNcsi:null NCSI 时安全返回
  • UpdateFirmwareVersionByNcsi_NotInitialized:未初始化时返回 false
  • UpdateFirmwareVersionByNcsi_FailedResponse:NCSI 失败时返回 false
  • StartNcsiUpdateTask_DifferentPackageIds:不同 package_id 均可启动
  • StopNcsiUpdateTask_MultipleTimes:多次 stop 不崩溃

修复

UpdateFirmwareVersionByNcsi_ZeroVersion 测试适配实现行为(snprintf_s 对全零返回失败,改为验证不崩溃)


新增7:huawei/hw_nvme_pcie4 测试扩充(39.2% → ~50%,5 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nvme/huawei/hw_nvme_pcie4/test_hw_nvme_pcie4.cpp 从 18 个扩充至 23 个用例

新增用例(5 个)

  • test_hw_nvme_pcie4_replace_dynamic_property_slot_and_devicename:同时替换 Slot 和 DeviceName
  • test_hw_nvme_pcie4_multiple_devices_independent:多实例独立性
  • test_hw_nvme_pcie4_init_multiple_system_ids:不同 SystemId 均可初始化
  • test_hw_nvme_pcie4_start_multiple_slots:多次 start/stop 循环

修复

移除不正确的 start(mc::dict) 调用(实际签名为 start())


新增8:moorethreads/mtt_s4000 gpu 测试扩充(67.4% → ~72%,5 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_gpu_card/moorethreads/mtt_s4000/test_mtt_s4000_gpu.cpp 从 13 个扩充至 18 个用例

新增用例(5 个)

  • mtt_s4000_gpu_stop_without_smbus:smbus 未初始化时 stop 不崩溃
  • mtt_s4000_gpu_stop_after_start:start 后 stop 正常执行
  • reset_default_properties_processor_fields:reset_default_properties 重置所有字段
  • mtt_s4000_gpu_multiple_init:同一实例多次 init 不崩溃

修复

设备键名从错误的 "Processor" 修正为 "GraphicsController"


新增9:vastai/vg1000 gpu 测试扩充(61.4% → ~65%,3 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_gpu_card/vastai/vg1000/test_vg1000_gpu.cpp 从 9 个扩充至 13 个用例(含析构取消 timer)

新增用例(4 个)

  • vg1000_gpu_stop_without_smbus:smbus 未初始化时 stop 不崩溃
  • vg1000_gpu_reset_properties_after_start_stop:start/stop 后 reset_default_properties 正常
  • vg1000_gpu_multi_init:同一实例多次 init 不崩溃
  • gpu_destructor_stops_timers_without_explicit_stop:析构取消周期 timer,避免 destroy 后 SIGSEGV

修复

设备键名从错误的 "Processor" 修正为 "GraphicsController"


新增10:wangxun/rp1000 optical_module 测试(61.1% → ~68%,17 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/wangxun/rp1000/test_rp1000_om.cpp 新建
tests/drivers/pcie_nic_card/wangxun/rp1000/meson.build test_rp1000_abi_sources 添加 test_rp1000_om.cpp

新增用例(17 个)

  • create_om_success:OpticalTransceiver 设备创建成功
  • create_om_null_service / create_om_null_name:nullptr 参数验证
  • start_stop_om_basic:基本 start/stop 流程
  • start_om_null_device / stop_om_null_device:nullptr 参数返回 STATUS_ERROR
  • init_om_null_device/csr/connector:各种 nullptr 参数验证
  • init_om_empty_config:空配置下 init 成功
  • reset_default_properties / reset_default_properties_multiple_times:reset 正常及多次不崩溃
  • stop_ncsi_update_task_without_start / stop_ncsi_update_task_multiple_times:安全性
  • multiple_start_stop / multi_instance_om:稳定性及多实例
  • default_constructor_and_destructor:默认构造析构验证

新增11:mellanox/interface/network_port 测试扩充(59.2% → ~63%,11 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/mellanox/interface/test_network_port.cpp 新增 11 个用例

新增用例(11 个)

  • UpdateMacAddr_NotInitialized / UpdateDefaultMacAddr_NotInitialized:未初始化 NCSI 时返回 false
  • SetLldpCapability_NotInitialized / GetLldpCapability_NotInitialized:未初始化 NCSI 时返回 false
  • UpdateMacAddr_MockFail / UpdateDefaultMacAddr_MockFail / GetLldpCapability_MockFail:mock MCTP 失败路径
  • SendNcsiTestPacket_NotInitialized / SetLLDPTxEnable_NotInitialized:未初始化路径
  • StopNcsiUpdateTask_NoStart_NoThrow / StopNcsiUpdateTask_StartThenStopTwice:安全性

新增12:wangxun/interface/network_adapter 测试扩充(59.7% → ~62%,6 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/wangxun/interface/test_network_adapter.cpp 新增 6 个用例

新增用例(6 个)

  • UpdateFirmwareVersion_NotInitialized:未初始化 NCSI 时返回 false
  • SetNetworkAdapterOem_Null:null oem 设置不崩溃
  • UpdateFirmwareVersionByNcsi_MockFail:mock MCTP 失败时返回 false
  • StopNcsiUpdateTask_MultipleTimes:多次 stop 不崩溃
  • StartNcsiUpdateTask_DifferentPackageIds:不同 package_id 均可启动

新增13:vastai/dc1000 测试(65.2% → ~72%,21 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_gpu_card/vastai/dc1000/test_dc1000_common.h 新建
tests/drivers/pcie_gpu_card/vastai/dc1000/test_dc1000_abi.cpp 新建,21 个测试用例
tests/drivers/pcie_gpu_card/vastai/dc1000/meson.build 新建

新增14:wangxun/ff50xx 测试(0% → ~30%,16 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/wangxun/ff50xx/test_ff50xx_common.h 新建
tests/drivers/pcie_nic_card/wangxun/ff50xx/test_ff50xx_abi.cpp 新建,16 个测试用例
tests/drivers/pcie_nic_card/wangxun/ff50xx/meson.build 新建

新增用例(16 个)

  • 驱动注册验证:register_device_driver_success、device_driver_structure_validation
  • PCIeNicCard 错误处理:nullptr 服务/名称/init/start/stop
  • NicPort 错误处理:nullptr 服务/init/start/stop
  • OpticalTransceiver 错误处理:nullptr 服务/init/start/stop
  • ff50xx_card 构造验证

新增15:meson.build 补全与新建测试文件

涉及文件

文件 修改说明
tests/drivers/pcie_gpu_card/vastai/interface/meson.build 将已存在但未纳入编译的 test_pcie_device.cpp 加入源文件列表
tests/drivers/pcie_gpu_card/nvidia/interface/meson.build 加入 test_gpu_read_methods.cpp、test_pcie_card_read_methods.cpp、test_memory_read_methods.cpp,新增 test_utils_dep 依赖
tests/drivers/pcie_nvme/samsung/interface/test_pcie_function.cpp 新建,11 个测试用例,覆盖 PCIeDevice_PCIeFunction::SetDeviceBDF 及信号触发
tests/drivers/pcie_nvme/samsung/interface/meson.build 加入 test_pcie_function.cpp

新增16:moorethreads/interface debounce 测试(75.9%↑,4 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_gpu_card/moorethreads/interface/test_gpu_read_temperature.cpp 新增 4 个用例

新增用例(4 个)

  • init_debounce_config(2 个用例)
  • debounce 路径(2 个用例):覆盖 temperaturedebounce 超阈值设置 GPU_TEMP_READ_FAILED 路径

新增17:vastai/interface debounce 和 init 测试(60.4%↑,4 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_gpu_card/vastai/interface/test_interface.cpp 新增 4 个用例

新增用例(4 个)

  • init_debounce_config(2 个用例)
  • read_temp_with_debounce
  • read_temp_checksum_fail_exceeds_debounce

新增18:chip/eeprom 直接单元测试(62.3%↑,6 个用例)

涉及文件

文件 修改说明
tests/drivers/chip/eeprom/test_eeprom_abi.cpp 新增 ChipEepromDirectTest(6 个用例)

新增用例(6 个)

  • parse_eeprom_header:解析 eeprom 头部
  • get_sdr_data:获取 sdr 数据
  • block_read / block_write:块读写操作
  • 访问器默认值验证
  • 结构体大小验证

新增19:nvidia/interface PCIeDevice + Processor_Metrics 测试(45.0%↑,13 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_gpu_card/nvidia/interface/test_pcie_card_read_methods.cpp 新增 13 个用例

新增用例(13 个)

  • pcie_device_bdf_test(6 个用例):覆盖 SetRootBDF(零值拒绝、成功)和 replace_dynamic_property(Slot、DeviceName、无占位符)
  • nvidia_processor_metrics_test(7 个用例):覆盖 init_debounce_config、read_gpu_power_consumption 各路径(null、空响应、成功、无效字符串、debounce 超限、debounce 窗口)

新增20:boardcom/interface/network_adapter 测试(38.9%↑,3 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/boardcom/interface/test_network_adapter.cpp 新增 3 个用例

新增用例(3 个)

  • SetNetworkAdapterOem_Null:null oem 设置不崩溃
  • StopPldmUpdateTask_AfterRestart:重启后 stop 不崩溃
  • StartPldmUpdateTask_NullPldm:null pldm 时不崩溃

新增21:hisi/interface/optical_module smbus 路径测试(53.8%↑↑,14 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/hisi/interface/test_optical_module.cpp 新增 optical_module_smbus_test fixture(14 个用例)

新增用例(14 个)

  • UpdatePresenceInfo_EmptyResponse / TooShort / Present / InvalidValue:presence smbus 各边界路径
  • UpdateStaticInfo_EmptyResponse / TooShort / ValidResponse:静态信息 smbus 路径
  • UpdateDynamicInfo_EmptyResponse / TooShort / NoChannels / WithChannels:动态信息 smbus 路径
  • UpdateAggregateInfo_EmptyResponse / TooShort / NoChannels:聚合信息 smbus 路径
  • Start1825SmbusTask_NullSmbus / ThenStop、Stop1825SmbusTask_WithoutStart

新增22:samsung/interface/nvme 测试(69.2%↑,5 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nvme/samsung/interface/nvme/test_nvme_status.cpp 新增 3 个用例
tests/drivers/pcie_nvme/samsung/interface/nvme/test_nvme_basic_management.cpp 新增 2 个用例

新增用例(5 个)

  • UpdateStatusByVpd_NotInitialized:未初始化时返回 false
  • StopVpdUpdateTask_NoStartSafe:未 start 时 stop 不崩溃
  • StopVpdUpdateTask_MultipleTimes:多次 stop 不崩溃
  • UpdateBasicManagementByVpd_NotInitialized:未初始化时返回 false
  • StopVpdUpdateTask_RestartThenStopTwice:重启后多次 stop 不崩溃

新增23:boardcom/interface/optical_module OpticalModule_Oem 测试(12.0%↑,6 个用例)

涉及文件

文件 修改说明
tests/drivers/pcie_nic_card/boardcom/interface/test_optical_module.cpp 新增 fixture BoardcomOpticalModuleOemTest(6 个用例)

累计覆盖率提升预估

模块 基线覆盖率 预估提升后 新增用例数
wangxun/interface/optical_module 1.8% ~70% 35+11
mellanox/interface/optical_module 11.9% ~70% 30+6
boardcom/interface/optical_module 12.0% ↑ 6
samsung/interface/pcie_device 7.1% ~80% 26+11
libraries/nvme (nvme_mi_vpd) 50.1% ~55% 12
libraries/pmbus 62.1% 修复 8 个失败用例 0
libraries/canbus 63.0% 修复链接问题 0
wangxun/interface/network_port 40.7% ~55% 12
mellanox/interface/network_adapter 31.7% ~45% 11
huawei/hw_nvme_pcie4 39.2% ~50% 5
moorethreads/mtt_s4000 67.4% ~72% 5
vastai/vg1000 61.4% ~65% 3
wangxun/rp1000 (om) 61.1% ~68% 17
mellanox/interface/network_port 59.2% ~63% 11
wangxun/interface/network_adapter 59.7% ~62% 6
vastai/dc1000 65.2% ~72% 21
wangxun/ff50xx 0% ~30% 16
nvidia/interface/pcie_device 45.0% ↑ 13
moorethreads/interface/processor 75.9% ↑ 4
vastai/interface/processor 60.4% ↑ 4
internal/chip/chip_eeprom 62.3% ↑ 6
boardcom/interface/network_adapter 38.9% ↑ 3
hisi/interface/optical_module 53.8% ↑↑ 14
samsung/interface/nvme 69.2% ↑ 5

总新增/修复测试用例:~250+ 个


构建验证结果

全量编译和测试均通过:

./scripts/smart_build.sh --clean
编译:1746/1746 成功
测试:93/93 全部通过(Fail: 0)

问题十九:test_scanner 定时器竞态与测试稳定性问题

19.1 现象

test_scanner 存在两类间歇性失败:

(1)断言失败 —— PRE_FAILURE 状态被跳过

test_handle_result_failure_through_reading_failure 用例中,ScanEnabled 置 1 后用固定 sleep_for(15ms) 等待一次失败读取,断言 Status == SCANNER_PRE_FAILURE。但在高负载或调度抖动时,15ms 内可能已经发生了两次失败读取,状态已穿越到 SCANNER_FAILURE,导致断言失败:

Expected equality of these values:
  status_after_one_failure
    Which is: 2        // SCANNER_FAILURE
  SCANNER_PRE_FAILURE
    Which is: 1

(2)SIGABRT 崩溃 —— pure virtual method called

测试结束时对象被销毁,但定时器回调仍在另一线程执行,通过已悬垂的 this 指针调用虚函数,此时虚表已释放:

pure virtual method called
terminate called without an active exception
Aborted (core dumped)

19.2 原因

问题A(时序敏感):handle_result_failure() 内部有防抖逻辑——连续失败次数达到阈值后状态从 PRE_FAILURE 跳转到 FAILURE。定时器周期为 10ms,固定 sleep_for(15ms) 无法可靠地只捕获"恰好一次失败"的中间状态。系统调度延迟、CPU 负载波动都可能导致 sleep 返回时已经过了多次定时器周期,状态已穿越 PRE_FAILURE 进入 FAILURE。

问题B(线程安全):定时器回调通过 lambda 捕获 this 指针访问 Scanner 成员。测试中 m_scanner 被销毁时,Scanner::~Scanner() 原为 = default(空析构),不会停止定时器。定时器回调仍在事件循环中排队,当回调被调度执行时,this 已悬垂,虚表内存已被释放或覆盖,通过虚表调用虚函数触发 pure virtual method called。

19.3 修改方案

A. Scanner 析构函数实现 —— 确保定时器在对象销毁前停止

scanner.h 中将 ~Scanner() = default 改为声明非默认析构函数:

Scanner() = default;
~Scanner();    // 原为 ~Scanner() = default;

scanner.cpp 中实现析构函数,调用 i_scanner.stop_scanner_task() 停止定时器:

Scanner::~Scanner()
{
    i_scanner.stop_scanner_task();
}

B. 测试 helper 函数 —— 轮询等待状态满足谓词条件

在 ScannerTest fixture 中新增模板辅助函数 wait_for_status_match,支持 lambda 谓词轮询等待:

// 轮询等待 Status 满足指定条件
template <typename Predicate>
bool wait_for_status_match(Predicate predicate, int timeout_ms = 2000)
{
    auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
    while (std::chrono::steady_clock::now() < deadline) {
        uint8_t status = m_scanner->i_scanner.Status.get_value().as<uint8_t>();
        if (predicate(status)) {
            return true;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
    }
    return false;
}

C. 测试用例改用谓词模式观察状态变化

原测试用固定 sleep + 精确断言中间状态:

// 旧代码:固定 sleep,精确断言中间状态
std::this_thread::sleep_for(std::chrono::milliseconds(15));
uint8_t status_after_one_failure = m_scanner->i_scanner.Status.get_value().as<uint8_t>();
EXPECT_EQ(status_after_one_failure, SCANNER_PRE_FAILURE);

std::this_thread::sleep_for(std::chrono::milliseconds(15));
uint8_t status_after_two_failures = m_scanner->i_scanner.Status.get_value().as<uint8_t>();
EXPECT_EQ(status_after_two_failures, SCANNER_FAILURE);

改为谓词轮询模式,在一次轮询中同时记录中间状态和等待最终状态:

// 新代码:谓词轮询,记录中间状态
bool observed_pre_failure = false;
ASSERT_TRUE(wait_for_status_match([&observed_pre_failure](uint8_t status) {
    if (status == SCANNER_PRE_FAILURE) {
        observed_pre_failure = true;
    }
    return observed_pre_failure && status == SCANNER_FAILURE;
}, 2000));
EXPECT_TRUE(observed_pre_failure);

谓词逻辑:每次轮询检查当前状态,如果观察到 SCANNER_PRE_FAILURE 则记录标志;当标志已设且状态变为 SCANNER_FAILURE 时返回 true,表示整个失败防抖过程完成且中间状态被捕获。

D. Scanner 类方法格式化优化

将 init()、start()、stop()、set_value()、from_variant()、dump() 等方法的花括号从 K&R 风格(开花括号在函数签名同一行)统一为 Allman 风格(开花括号独占一行),以及 dump() 中 oss << 的格式化调整,提升代码可读性。

19.4 原理

Scanner 析构函数解决问题B(线程安全):遵循 RAII(Resource Acquisition Is Initialization)原则——资源在构造时获取,在析构时释放。定时器是 Scanner 对象持有的资源,对象销毁时应自动停止定时器,而不是依赖调用者手动调用 stop()。

析构函数中调用 i_scanner.stop_scanner_task() 确保:

  1. 定时器在对象内存被释放之前停止
  2. 已排队的回调不会再被调度执行(或在执行前被取消)
  3. 不依赖测试代码显式调用 stop() —— 即使测试忘记调用或异常退出,析构函数也会自动清理

这与问题十四(Hi1822FC TearDown 定时器 use-after-free)和问题十五(MCTP m_destroying 缺陷)属于同一类问题——异步回调与对象生命周期的同步。不同的是,本次修复直接在产品代码的析构函数中解决,而非在测试 TearDown 中打补丁。

wait_for_status_match 解决问题A(时序敏感):用轮询替代固定 sleep,以 5ms 为间隔持续检查状态,最多等待 2000ms。这样无论定时器实际触发的时序如何,都能可靠地捕获状态变化过程,消除了对固定延时精度的依赖。

19.5 涉及文件

文件 修改说明
drivers/scanner/scanner/scanner.h ~Scanner() = default → ~Scanner() 声明非默认析构函数
drivers/scanner/scanner/scanner.cpp 新增 Scanner::~Scanner() 实现(调用 i_scanner.stop_scanner_task());方法花括号风格统一为 Allman;dump() 格式化调整
tests/drivers/scanner/test_scanner.cpp 新增 wait_for_status_match 模板辅助函数;test_handle_result_failure_through_reading_failure 改用谓词轮询模式

问题二十:test_nv_common_abi 栈对象用例 SIGSEGV 与生命周期防护

20.1 日志事实与定位边界

test_failure_logs/test_58.log 中,93 个测试目标有 92 个通过,test_nv_common_abi 以 SIGSEGV 退出。最后输出是:

[ RUN      ] NvCommonAbiTest.nv_common_gpu_default_constructor_and_destructor

该进程总耗时仅 0.14 秒,日志没有调用栈,不能仅凭最后一行判定崩溃发生在析构函数。GPU 两个定时器的首次到期时间分别为 5 秒和 10 秒;因此这份日志不足以证明本次崩溃由到期回调或其读取的未初始化指针引起。精确根因仍需原始构建的 core 或可复现调用栈。

20.2 代码审核确认的风险

  • init() 即便缺少 RefChip,仍创建并启动两个捕获 this 的周期定时器。
  • 当前 mc::timer::is_active() 检查是否取消以及是否尚未到期。到期后、回调排队或执行期间可能返回 false,不能据此省略 stop()。
  • mc::timer::stop() 取消定时器并等待当前回调结束(回调自身线程调用时不等待);timer 析构使用不等待的取消。释放成员 shared_ptr 也不代表 timer 立即销毁,因为父对象和在途任务可能仍持有引用。
  • 原默认析构没有在 GPU 成员销毁前主动停 timer;m_common_card / m_common_memory 缺少类内初始化,默认初始化的栈对象会留下不确定指针值。这些是应修复的潜在缺陷,但不是日志已经证实的直接原因。

20.3 优化后的方案

产品代码:~nv_common_gpu() 调用 stop();每个 timer 一次 timer->stop() 再 reset()(libmcpp 周期重装不再清取消标志,一次 stop() 即可挡住下一拍);两个缓存指针初始化为 nullptr。

测试:独立栈对象保留 detach_gpu_owner(),隔离没有父设备树时的相对路径属性解析;检查 init/start/stop 返回值,并验证重复 stop()。新增 gpu_destructor_stops_timers_without_explicit_stop:外部持有两个 timer,GPU 不显式 stop 就离开作用域,再断言两个 timer 均已停止。测试将 timer 周期延长到一小时,避免负向验证时触发已经失效的 GPU 回调;is_active()/stop() 只访问 timer::m_impl,结束时 clear() 释放外部引用。

该回归可区分“析构主动取消”与“仅释放成员引用”。周期重装窗口由 libmcpp 一次 stop() 关闭;这仍不能证明 libmcpp timer 在任意线程交错下的完整并发语义,只覆盖本驱动的停止路径。

20.4 修正原方案中的误解

当前 libmcpp 的普通属性通知直接调用信号与观察者,并非 set_value() 必然 post 到 executor。bind_executor() 只绑定对象自身,子 timer 不继承父对象直接绑定的 executor。因此这里移除 bind_serial_executor(device) / drain_executor(device);不能将它们视为两个 timer 的同步屏障。

detach_gpu_owner() 仅用于无父对象树的测试隔离,不承担异步任务清理职责。定时器实现、属性通知路径分别见 subprojects/libmcpp/libraries/mcbase/src/timer.cpp、subprojects/libmcpp/libraries/mcengine/src/property/helper.cpp;executor 查找规则见 subprojects/libmcpp/libraries/mcbase/src/object.cpp。

20.5 涉及文件

文件 修改说明
drivers/pcie_gpu_card/nvidia/nv_common/nv_common_gpu.h 声明析构函数;两个缓存指针初始化为 nullptr
drivers/pcie_gpu_card/nvidia/nv_common/nv_common_gpu.cpp 析构调用 stop;每个 timer 一次 stop 再 reset
tests/drivers/pcie_gpu_card/nvidia/nv_common/test_nv_common_gpu.cpp 栈对象用例检查返回值;新增析构停止定时器回归
tests/drivers/pcie_gpu_card/nvidia/nv_common/README.md 测试覆盖、运行命令与定位边界

问题二十一:网迅 RP1000/SFX00HT 压测概率 SIGSEGV

21.1 现象与复现

2026-09-08 的 test_failure_logs 中有 7 次 test_rp1000_abi、2 次 test_sfx00ht_abi SIGSEGV,进程运行约 0.16 秒,最后用例分散在 PCI ID、四元组及电源状态测试。test_88.log 是 SIGTERM,与这 9 次段错误分开处理。

修复前按原 wrapper 与运行库路径重复启动独立进程可以复现。gdb 在 RP1000 和 SFX00HT 捕获到相同 worker 调用栈:

mc::timer::timer_impl::start 的 async_wait 完成回调
  -> shared_ptr 释放最后一个 timer 引用
  -> mc::timer::~timer
  -> mc::object::cleanup_on_destroy
  -> parent->remove_child
  -> mc::object_impl::remove_child
  -> basic_shared_mutex::try_lock  [SIGSEGV]

21.2 根因

mc::timer::stop() 会取消等待并等待业务 timeout 回调,但底层 async_wait 的取消完成通知仍可稍后执行。该通知临时提升 timer 的弱引用,最后一份引用可能在 ABI 设备已经 delete 后才释放;timer 随后通过 parent 回访已释放的 card 或内嵌接口。

ABI card 由裸 new 创建,接口又按值嵌入 card,不能假设这些对象受 mc::shared_ptr 管理。对这种 parent 调用 set_parent(nullptr) 也不能作为可靠补救:旧 parent 的弱引用已经无法安全提升,清理本身仍可能访问失效的内嵌计数器。

子对象不会继承父对象直接绑定的 executor。timer 去掉 parent 后必须显式保存启动时宿主解析出的 executor;这只约束 timer 的执行位置,不能把 card 上的 executor 屏障视为已经排空所有取消完成通知。

21.3 修复

  • 9 个共享 NCSI 接口 timer 改为无 parent 构造,由成员指针直接持有,并显式 bind_executor(executor())。
  • 重复启动前停止并释放旧任务,避免覆盖成员后遗失旧 timer。
  • 释放路径统一走 stop_owned_timer():timeout.disconnect_all() + 一次 stop() + reset()。start/stop/stop_owned_timer 不得从 timeout 业务回调调用;改间隔用 set_interval()。析构 stop 须先于 service/io 停机,否则阻塞 stop() 可能挂起。libmcpp 周期重装不再清取消标志,一次 stop() 即可挡住下一拍,不必再补第二次 stop()。
  • RP1000/SFX00HT/FF50xx(含 PLDM)四元组首次到期后只 set_interval(10s),由库在回调返回后重装,不再在 timeout 回调里 stop()/start()。
  • RP1000/SFX00HT 四元组 timer 使用相同所有权方式;card 析构在成员销毁前停止四元组及 card 内嵌 NCSI 任务。

修复范围是成员 timer 生命周期;MCTP endpoint/retry 等其他异步回调仍遵循各自的停止约定。

21.4 回归覆盖

test_timer_lifecycle.cpp 对 PCIeDevice、Cooling、PCIeFunction、NetworkPort、LinkInfo、Metrics、OpticalModule、Power、TemperatureCelsius 共 9 种接口覆盖裸对象和内嵌对象,断言 timer 不进入宿主 children,并用协议弱引用验证启动、重复启动、停止与析构释放。

test_timer_stop.cpp 使用 Cooling 真实 timeout 和 mock request 握手:在放行 gate 前轮询 stop_finished 仍为 timeout,确认业务回调结束前 stop 不返回。随后靠 stop_returned_during_request 做最终断言。该用例仍不用于证明所有 asio 内部交错。

RP1000/SFX00HT/FF50xx 生命周期用例的 OEM 探针与 NCSI 端口/光模块启停辅助抽到 tests/drivers/pcie_nic_card/wangxun/lifecycle_helpers.h,PLDM 侧复用同一探针模板。

21.5 验证

  • test_wangxun_interface、test_rp1000_abi、test_sfx00ht_abi 定向测试通过。
  • 修复前实现的隔离负向对照能够触发 timer parent 所有权断言,修复实现通过。
  • RP1000/SFX00HT 各串行 500 轮、并发 500 轮,共 2,000 次独立进程、314,000 个用例通过。
  • 当时全量 93/93 个测试目标通过;构建没有启用 sanitizer 插桩。
LD_LIBRARY_PATH="$PWD/.lua_runtime_libs${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
  meson test -C builddir test_wangxun_interface test_rp1000_abi test_sfx00ht_abi \
  --print-errorlogs --wrapper "$PWD/builddir/meson_test_wrapper.sh"

21.6 NCSI 设备树析构补强

FF50xx NCSI 的 card timer 使用与 RP1000/SFX00HT 相同的无 parent 所有权。三类 card 在析构函数体中先停止自身 timer 和当前子端口;端口在其 OEM 等成员析构前停止内嵌接口及当前光模块;光模块同样在成员析构前停止轮询。NetworkPort Metrics 也纳入统一停止路径,未初始化的光模块指针显式初始化为 nullptr。

card/port 使用 get_children() 的强引用快照停止当前对象树。节点 detach 或 reparent 前,调用方必须先停止任务并重新绑定 OEM 等依赖;旧 owner 不保留历史 weak cache,避免在节点转移后停止新 owner 的任务。已有 m_network_ports 裸指针列表仍按原生命周期约定使用,本次不扩大为动态迁移保证。

这部分处理的是 NCSI timer 与 OEM 成员的析构顺序,不代表 MCTP endpoint/retry 回调生命周期已经闭环。

21.7 NCSI 设备树回归

新增 19 个生命周期回归:RP1000 7 个、SFX00HT 4 个、FF50xx 8 个。用例通过外部保留的当前子节点和 OEM 析构探针验证 card、port、光模块在成员失效前停止轮询。隔离负向变体去掉 port/光模块析构清理后,OEM 顺序探针能够失败。

定向验证结果:

  • test_wangxun_interface:146 个用例通过。
  • test_rp1000_abi:167 个用例通过。
  • test_sfx00ht_abi:158 个用例通过。
  • test_ff50xx_abi:98 个用例通过。
  • git diff --check 通过。
LD_LIBRARY_PATH="$PWD/.lua_runtime_libs${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
  meson test -C builddir test_wangxun_interface \
  test_rp1000_abi test_sfx00ht_abi test_ff50xx_abi \
  --print-errorlogs --wrapper "$PWD/builddir/meson_test_wrapper.sh"

21.8 FF50xx PLDM 对齐

9 个 PLDM 接口 timer 使用与 NCSI 接口相同的无 parent、成员持有和显式 executor 绑定方式。FF50xx PLDM 的 120 秒四元组 timer、60 秒 FRU/PDR 缓存 timer,以及 card、port、光模块析构停止路径一并对齐;停止范围只包含当前对象树。

PLDM 与 NCSI 实现包含同名 dev 类和反射注册,因此新增独立的 test_wangxun_pldm_lifecycle 可执行目标。测试依赖同时修正为链接 libff50xx_pldm,避免错误加载 NCSI libff50xx。

PLDM 生命周期测试使用同步 MCTP mock,验证 timer 所有权和业务回调停止边界,不覆盖真实 MCTP endpoint/retry 的异步 teardown;该传输层仍需要独立的 cancel-and-wait 设计。

21.9 PLDM 与全量验证

  • test_wangxun_pldm_lifecycle:25 个用例通过,其中 18 个接口生命周期用例、7 个 FF50xx PLDM 设备树用例。
  • test_ff50xx_abi:98 个用例通过,确认 PLDM 独立目标未污染 NCSI DSO/反射注册。
  • 全量 94/94 个测试目标通过。
  • git diff --check 通过;构建没有启用 sanitizer 插桩。
LD_LIBRARY_PATH="$PWD/.lua_runtime_libs${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
  meson test -C builddir test_wangxun_pldm_lifecycle test_ff50xx_abi \
  --print-errorlogs --wrapper "$PWD/builddir/meson_test_wrapper.sh"

LD_LIBRARY_PATH="$PWD/.lua_runtime_libs${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
  meson test -C builddir --print-errorlogs \
  --wrapper "$PWD/builddir/meson_test_wrapper.sh"

问题二十二:Mellanox interface 压测概率 SIGSEGV

22.1 现象与复现

2026-09-09 的 test_failure_logs 中 3 次 test_mellanox_interface SIGSEGV(test_10.log、test_22.log、test_86.log),进程运行约 0.34 秒。最后 [ RUN ] 均为 NetworkAdapterCoolingTest.UpdateChipTempByNcsi_Success。stderr 里的 Error: Invalid BDF values 来自同进程更早的 PCIeDeviceTest 旁路日志,不是断言失败。

同一进程里前面大量 PCIeDeviceTest 启停 timer(含 TimerCallback_*),崩溃落在 Cooling 套件开头。CX4/CX5/CX6 卡源码没有 make_shared<mc::timer>,本轮日志也未失败。

根因与问题二十一相同:ABI 用裸 new + destroy_device() 直接 delete,不调用 stop()。mc::make_shared<mc::timer>(this) 把 timer 挂进对象树后,迟到的 async_wait 完成回调在 ~timer → remove_child 时回访已释放的内嵌接口 / 未托管 parent。

22.2 修复

对齐网迅接口层,只改 Mellanox interface 的 7 处 timer(卡级没有 timer):

  • cooling.cpp、pcie_device.cpp、pcie_device/pcie_function.cpp
  • network_port.cpp、network_port/link_info.cpp、network_port/metrics.cpp
  • network_adapter/oem.cpp

做法:

  • mc::make_shared<mc::timer>() 无 parent,再 bind_executor(executor())。
  • 释放路径统一走 stop_owned_timer():timeout.disconnect_all() + 一次 stop() + reset()。析构 stop 须先于 service/io 停机。libmcpp 周期重装不再清取消标志,一次 stop() 即可挡住下一拍。
  • 启动前先 stop_ncsi_update_task(),避免覆盖成员后遗失旧 timer。start/stop/stop_owned_timer 不得从 timeout 业务回调调用。
  • PCIeDevice 原先 ~PCIeDevice() = default,改为析构函数体先停轮询(C++ 先跑析构体,再反序拆成员)。其余 6 个接口头文件里本来就有析构 stop。

NetworkPort::start_ncsi_update_task_mellanox 仍按历史硬编码 10s 间隔,不改业务周期。

22.3 回归覆盖

tests/drivers/pcie_nic_card/mellanox/interface/test_timer_lifecycle.cpp 对上述 7 种接口做 typed test:裸 unique_ptr 与内嵌 holder,断言 children.empty(),并用协议 weak_ptr 验证重复启动、停止与析构释放。

22.4 验证

LD_LIBRARY_PATH="$PWD/.lua_runtime_libs${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
  meson test -C builddir test_mellanox_interface --print-errorlogs \
  --wrapper "$PWD/builddir/meson_test_wrapper.sh"