
F´ 框架中的 FW_ASSERT 到 FATAL 事件转换Svc::AssertFatalAdapter 组件深度解析【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/gh_mirrors/fp/fprime导读Svc::AssertFatalAdapter是 F´F Prime飞行软件与嵌入式系统框架中的一个被动组件passive component其核心职责是拦截系统中所有FW_ASSERT断言调用并将每一次断言失败转换为对应的 FATAL致命事件交给框架中既有的 FATAL 事件处理机制如 ActiveLogger、FatalHandler统一处置。阅读本文后你将掌握该组件的设计原理、Fw::AssertHook挂钩机制、FW_ASSERT_LEVEL编译期配置的三种形态以及如何通过单元测试验证 06 参数断言与异常断言事件的全部分发逻辑从而在自有 F´ 部署中正确接入并深度定制断言失败处理链路。1. 组件定位与需求基线Svc::AssertFatalAdapter的设计说明SDD 文档明确指出Svc::AssertFatalAdapteris a passive component that intercepts calls to FW_ASSERT and issues FATAL events for each.即它是一个被动组件拦截FW_ASSERT调用并为每一次断言失败发出 FATAL 事件。该行为由一个明确的工程需求约束Verification Method 为单元测试RequirementDescriptionVerification MethodAF-001TheSvc::AssertFatalAdaptercomponent shall convert all calls to FW_ASSERT to FATAL eventsUnit test这一需求在仓库的 FPP 组件定义 中同样以注释形式体现A component for turning FW_ASSERTs into FATALs组件类型声明为passive component AssertFatalAdapter。从 FPP 定义可以看出该组件的接口面非常精简仅包含三类特殊端口Port类型用途Logevent port发出二进制格式的 FATAL 事件LogTexttext event port发出人可读的文本事件Timetime get port获取事件时间戳SDD 文档 3.1.2 节对端口的描述与此一致“TheSvc::AssertFatalAdaptercomponent uses only the log infrastructure ports.” 整个组件不包含任何命令、遥测、数据产品或其他业务端口其设计目标极其单一——把断言失败“翻译”成事件后立即转交日志基础设施。AssertFatalAdapter 组件图上图来自 SDD 文档 3.1.1 节展示了组件与外部 Log、LogText、Time 基础设施的连接关系。2. 工作原理基于 Fw::AssertHook 的挂钩机制2.1 FW_ASSERT 宏的编译期形态要理解AssertFatalAdapter首先要理解它拦截的对象——FW_ASSERT宏。该宏定义于 Fw/Types/Assert.hpp其行为由config/FpConfig.h中的FW_ASSERT_LEVEL决定默认值为FW_FILENAME_ASSERT见 config/FpConfig.h取值FILE_NAME_ARG类型断言文件信息的编码方式FW_NO_ASSERTconst CHAR*断言完全关闭FW_ASSERT(...)退化为空操作((void)(first_arg))只保留表达式求值不触发任何断言逻辑FW_FILEID_ASSERTU32文件以编译期分配的 IDASSERT_FILE_ID32 位整数形式传入节省 flash/带宽FW_RELATIVE_PATH_ASSERTconst CHAR*文件以相对路径ASSERT_RELATIVE_PATH传入FW_FILENAME_ASSERT默认const CHAR*文件以完整__FILE__字符串传入无论采用哪种形态宏展开后的核心逻辑都一致当第一个参数断言条件为假时调用Fw::SwAssert(file, arg1, arg2, ..., lineNo)。Fw::SwAssert在 Fw/Types/Assert.hpp 中提供了 0 到 6 个断言参数共 7 个重载版本全部带有CLANG_ANALYZER_NORETURN注解以帮助静态分析。2.2 AssertHook断言回调的注册基类同一头文件中定义了Fw::AssertHook基类Fw/Types/Assert.hpp它是整个拦截机制的枢纽class AssertHook { public: AssertHook() : previousHook(nullptr){}; virtual ~AssertHook(){}; virtual void reportAssert(FILE_NAME_ARG file, NATIVE_UINT_TYPE lineNo, NATIVE_UINT_TYPE numArgs, FwAssertArgType arg1, FwAssertArgType arg2, FwAssertArgType arg3, FwAssertArgType arg4, FwAssertArgType arg5, FwAssertArgType arg6); virtual void printAssert(const CHAR* msg); virtual void doAssert(); // 默认调用 assert() void registerHook(); void deregisterHook(); private: AssertHook* previousHook; // 链式保存上一个 hook支持多个 hook 叠加 };其运行方式为系统默认的断言流程在失败时构建消息并回调已注册的AssertHook。AssertFatalAdapter正是通过在组件构造时将自己实现的AssertHook子类注册进系统registerHook()从而接管每一次断言失败回调。2.3 私有子类与注册时序在 AssertFatalAdapterComponentImpl.hpp 中组件内部声明了一个私有的嵌套类AssertFatalAdapterComponentImpl::AssertFatalAdapter它继承自Fw::AssertHook重写reportAssert(...)收到断言回调后转发给外层组件重写doAssert()空实现源码注释为 “do nothing since there will be a FATAL”即不再执行默认的assert()终止动作因为 FATAL 事件的处理机制会负责后续处置保存AssertFatalAdapterComponentImpl* m_compPtr指针用于回调外层组件。注册发生在组件构造函数中AssertFatalAdapterComponentImpl.cppAssertFatalAdapterComponentImpl::AssertFatalAdapterComponentImpl(const char *const compName) : AssertFatalAdapterComponentBase(compName) { // register component with adapter this-m_adapter.regAssertReporter(this); // register adapter this-m_adapter.registerHook(); }两步操作分别完成“把组件自身登记为回调目标”和“把 hook 挂进系统的断言回调链”。3. 断言到 FATAL 事件的完整转换流程3.1 事件定义按参数个数拆分的 8 个 FATAL 事件转换的目标事件定义在 AssertFatalEvents.fppi 中共 8 个全部为severity fatal| 事件 | ID | 参数 | 格式串 | | ---- | -- | ---- | ------ | |AF_ASSERT_0| 0 | file, line |Assert in file {}, line {}| |AF_ASSERT_1| 1 | file, line, arg1 |Assert in file {}, line {}: {}| |AF_ASSERT_2| 2 | file, line, arg1, arg2 |Assert in file {}, line {}: {} {}| |AF_ASSERT_3| 3 | file, line, arg1..arg3 |Assert in file {}, line {}: {} {} {}| |AF_ASSERT_4| 4 | file, line, arg1..arg4 |Assert in file {}, line {}: {} {} {} {}| |AF_ASSERT_5| 5 | file, line, arg1..arg5 |Assert in file {}, line {}: {} {} {} {} {}| |AF_ASSERT_6| 6 | file, line, arg1..arg6 |Assert in file {}, line {}: {} {} {} {} {} {}| |AF_UNEXPECTED_ASSERT| 7 | file, line, numArgs |Unexpected assert in file {}, line {}, args {}|其中file参数的类型为string size AssertFatalAdapterEventFileSize其容量在运行时受FW_ASSERT_TEXT_SIZE默认 256见 config/FpConfig.h与FW_LOG_STRING_MAX_SIZE默认 200见 config/FpConfig.h共同约束详见后文单元测试对截断的处理。之所以按参数个数拆分成多个事件是因为 F´ 事件模型的参数列表是编译期固定的——不同个数的断言参数必须对应不同的事件定义这保证了事件携带的现场信息文件、行号、最多 6 个用户附加参数在反序列化后可以完整还原。3.2 reportAssert 的实现细节组件级reportAssert实现AssertFatalAdapterComponentImpl.cpp完整展示了转换链路关键步骤包括文件信息编码适配根据FW_ASSERT_LEVEL决定文件参数的呈现方式——FW_FILEID_ASSERT模式下将U32文件 ID 格式化为0x%08 PRIX32十六进制字符串其余模式直接以字符串传递。构建人类可读消息调用Fw::defaultReportAssert(...)把断言信息格式化为文本并通过printf(%s\n, msg)输出到标准输出。源码注释特别说明这里刻意避免使用fprintf(stderr, ...)因为 stderr 在操作系统层是不带缓冲的会在栈上分配大缓冲区与嵌入式环境通常较小的栈空间冲突。端口连接保护在发出事件前检查isConnected_Log_OutputPort(0)若端口尚未连接则直接assert(0)返回避免向空端口发事件导致更隐蔽的错误。按参数个数分发switch (numArgs)将 06 个参数分别映射到AF_ASSERT_0~AF_ASSERT_6default分支即参数个数超过 6 或非法统一走AF_UNEXPECTED_ASSERT。参数值在落入事件时统一经static_castU32(argN)转换其中argN的类型为FwAssertArgType即NATIVE_UINT_TYPE。3.3 嵌套 hook 的兜底路径外层reportAssert并非总能被调用——如果嵌套类构造时组件尚未注册m_compPtr nullptr则 AssertFatalAdapterComponentImpl.cpp 中的兜底逻辑会打印Svc::AssertFatalAdapter not registered!并执行assert(0)保证“即使组件初始化异常断言也不会被无声吞掉”。3.4 与 FATAL 处理机制的衔接SDD 文档 3.2 节 Functional Description 强调了这一设计的最终效果Whatever mechanism in the system that deals with FATAL events will handle the asserts via that mechanism.即组件本身不决定断言失败后的处置策略停机、重启还是仅记录而只是把断言“翻译”成 FATAL 事件送入事件流。系统内处理 FATAL 事件的组件如 Svc/ActiveLogger、Svc/FatalHandler 等会按既定的致命错误策略统一响应。这也是为何嵌套类把doAssert()重写为空实现——真正的“致命动作”已交由事件处理链完成避免双重处置。4. 状态与算法SDD 文档 3.4、3.5 节明确说明StateSvc::AssertFatalAdapter没有任何状态机Algorithms该组件没有显著的算法。从源码结构看组件实例内唯一的成员是嵌套的AssertFatalAdapter m_adapterAssertFatalAdapterComponentImpl.hpp组件自身完全是被动响应——由 hook 机制驱动无内部状态、无周期任务、无队列是典型的“事件翻译器”形态。这也与 FPP 中的passive component声明吻合SDD 图片中组件同样标注为passive。5. 单元测试AF-001 的验证方式需求的验证方法为单元测试测试代码位于 AssertFatalAdapterTester.cpp入口为AssertFatalAdapterTester::testAsserts()。它系统性地覆盖了转换逻辑的全部分支06 参数断言全覆盖依次触发FW_ASSERT(0)、FW_ASSERT(0,1)...FW_ASSERT(0,1,2,3,4,5,6)每次触发后用ASSERT_EVENTS_AF_ASSERT_N_SIZE/ASSERT_EVENTS_AF_ASSERT_N校验对应事件的产生数量与参数内容file、lineNo 及每个附加参数值。编译期配置感知测试通过#if FW_ASSERT_LEVEL FW_NO_ASSERT分支把期望事件数置为 0“Asserts may be turned off resulting in this component doing a no-op”否则为 1精确验证了断言关闭场景下组件退化为空操作的预期。文件字段截断处理测试按FW_MIN(FW_MIN(AssertFatalAdapterEventFileSize, FW_LOG_STRING_MAX_SIZE), FW_ASSERT_TEXT_SIZE)计算文件字段的最大可用长度并使用Fw::StringUtils::string_copy进行安全拷贝保证预期值与事件实际承载的截断后内容一致。文件 ID 编码同样按FW_ASSERT_LEVEL FW_FILEID_ASSERT分支构造0x%08 PRIX32格式的期望文件字符串与实现中的编码逻辑对齐。异常断言路径直接调用component.reportAssert(unexpectedFile, 1000, 10, 1, 2, 3, 4, 5, 6)传入 10 个参数超出支持上限断言触发AF_UNEXPECTED_ASSERT事件且numArgs 10验证default分支。此外测试基座AssertFatalAdapterTester通过connectPorts()将组件的Time、Log、LogText三个输出端口分别接到测试端的输入端口并在textLogIn回调中将文本事件打印到标准输出方便在测试运行中直接观察格式化后的事件文本。6. 接入方式与配置要点6.1 在拓扑中实例化由于组件只依赖Log、LogText、Time三个端口在 F´ 拓扑中接入非常直接实例化AssertFatalAdapter将它的Log/LogText接到事件聚合组件如 ActiveLogger 的输入将Time接到系统时间源即可。无需额外配置参数——组件构造时即完成 hook 注册实例化即生效。6.2 关键编译期配置配置项定义位置默认值影响FW_ASSERT_LEVELconfig/FpConfig.hFW_FILENAME_ASSERT决定FILE_NAME_ARG类型、文件信息的编码方式以及断言是否整体关闭FW_NO_ASSERTFW_ASSERT_TEXT_SIZEconfig/FpConfig.h256断言描述文本缓冲区大小参与文件字段容量的下限约束FW_LOG_STRING_MAX_SIZEconfig/FpConfig.h200日志字符串参数类型的最大长度同样约束事件 file 字段容量需要注意的是FW_ASSERT_LEVEL是全局编译期配置影响整个 F´ 二进制中的FW_ASSERT宏展开而非仅作用于本组件。部署前应结合目标平台的 flash/带宽预算与调试需求文件 ID 压缩 vs. 可读文件名做出选择。6.3 一个易踩的坑组件在Log端口未连接时无法发出 FATAL 事件此时实现会退化为assert(0)。因此在拓扑中务必先连接Log/LogText/Time端口再允许断言发生否则组件在初始化阶段触发的断言将回到原始 assert 行为无法体现“转换为 FATAL 事件”的设计意图。7. 变更记录SDD 文档的 Change Log 记录了组件的演进起点DateDescription10/16/2016Implementation and unit tests此后组件在仓库中持续演进如引入FW_FILEID_ASSERT支持、Fw::Logger::logMsg兜底输出、端口连接保护等但核心设计——被动组件 AssertHook 拦截 按参数个数分发 FATAL 事件——始终未变。结语Svc::AssertFatalAdapter是 F´ 断言失败处理链路中承上启下的关键一环向下它通过Fw::AssertHook挂钩机制捕获每一次FW_ASSERT向上它把包含文件、行号与最多 6 个附加参数的完整现场信息编码为 8 个 FATAL 事件AF_ASSERT_0~AF_ASSERT_6与AF_UNEXPECTED_ASSERT送入事件流。理解它的编译期配置FW_ASSERT_LEVEL、事件定义AssertFatalEvents.fppi与实现细节AssertFatalAdapterComponentImpl.cpp即可在自己的 F´ 部署中正确接入该组件并通过其单元测试模式AssertFatalAdapterTester.cpp验证断言转换行为的正确性。【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/gh_mirrors/fp/fprime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考