Files

6.5 KiB
Raw Permalink Blame History

ThreadX Modules:module preamble / dispatch request ID / XIP

模块里的 API 调用**可能根本不是普通的 API 调用**:
Module
  → 预定义的 request ID
  → 软件 dispatch 函数
  → 常驻 Module Manager
  → 真正的 ThreadX 服务

所以在模块里看到:

mov  request_id, #37
branch common_dispatch

不要判"大量功能被一个奇怪的 dispatcher 混淆了"——它很可能就是 ThreadX Module 的 ABI。真正该恢复的是 request ID → 服务名的映射(本质接近一张 syscall table)。 </CORE RULE>

一、Module Manager:常驻的唯一一份

  • Module Manager 是单份常驻代码,负责创建模块的初始线程、启动其执行,并承接模块发出的全部 ThreadX API 请求
  • 源文件以 txm_module_manager_* 命名;模块侧文件不带 "manager"(如 txm_thread_relinquish.c),共用 txm_module.h
  • 组成:模块装载器(加载/重定位/准备)、API dispatch、内存管理、线程管理
  • 同时在载的模块数量没有上限(受可用内存限制),但 Module Manager 只有一份

二、module preamble:必须在模块的第一个地址

布局固定:

[module preamble][module instruction area][module RAM area]

preamble 描述模块的基本特征与资源:

内容 说明
module ID / 主次版本 / preamble 大小 标识与格式版本
properties 位图 见下
shell 入口 模块 shell 入口点
start / stop 线程入口 模块的主线程入口
start / stop 线程优先级与栈大小 线程配置
callback 线程入口 / 优先级 / 栈大小 回调线程配置
代码大小 / 数据大小 区域尺寸

properties 位图(含义各不相同):

  • bit 0:特权模式 vs 用户模式(用户模式 = MMU 保护)
  • bit 1:无 MPU 保护 vs MPU 保护(MPU 保护要求用户模式)
  • bit 2:共享/外部内存访问的开关
  • bits 31–24:编译器 ID(区分 IAR / ARM / GNU 等工具链)

开发者常改的字段偏移(例如 module ID 在 0x10、start 线程在 0x1C、优先级在 0x24、栈大小在 0x28)在不同版本/工具链下要现场核对,不要跨版本照搬。

模块还需用位置无关代码/数据(PIC/PID)构建,才能被放到任意内存区执行。

三、request ID 与软件 dispatch

模块通过 txm_* 版本的 API 调 ThreadX,例如:

(_txm_module_kernel_call_dispatcher)(TXM_THREAD_RELINQUISH_CALL, 0, 0, 0);
  • Module Manager 提供该函数指针,它带着某个服务对应的 ID 调用 Module Manager 的 dispatch 函数,由后者执行真正的 API
  • 在模块源码里定义 TXM_MODULE 可把 ThreadX API 调用重映射为这些模块专用版本
  • ARM 上非特权模块用 SVC 指令陷入内核(特权)模式执行 API;若 preamble 把模块配为特权模式,则可直接调 API,不陷入

RE 主线:把 dispatch 调用点收集起来,建 request ID → 服务名的映射表,之后所有模块代码才可读。

四、装载方式与 XIP

  • 模块的指令区可以原地执行(XIP),也可以被拷贝进模块内存区后再执行;模块的数据内存需求总是从模块内存区分配
  • 因此可能出现:
代码地址完全不在 module RAM 分配范围内
  → 不一定是控制流劫持
  → 可能只是 XIP 模块(代码还在 Flash,数据在 RAM)
  • 唯一的硬要求还是:preamble 永远在模块第一个地址
  • 加载流程:分配内存 → 校验完整性 → 处理 preamble → 重定位代码与数据 → 初始化 → 创建模块线程 → 启动

五、线程与栈的两个细节

  • 每个模块提供一个线程入口及其栈大小/优先级/模块 ID,另有 callback 线程的栈大小/优先级
  • 模块线程在受内存保护的内核态执行时会用到另一个内核栈(由端口配置给出)
  • 模块线程栈顶存放线程入口信息结构(TXM_MODULE_THREAD_ENTRY_INFO 一类)——算栈用量时必须把它计入,否则会误判栈溢出
  • 模块拥有完整的 ThreadX API 访问权,包括在模块内创建更多线程

六、内存保护是可选的

启用时,只有该模块的线程能访问其代码与数据内存;任何越界访问都会造成内存故障并终止出错的线程。因此"某线程被静默终止"可能来自这里,而不是看门狗。

决策树

拿到 ThreadX Modules 目标
├─ 先定位 module preamble(必然在模块第一个地址)
│    ├─ 读 properties:特权/用户模式、MPU 保护、共享内存、编译器 ID
│    └─ 读线程入口/优先级/栈大小与代码/数据尺寸
├─ 看到大量「填 request ID → 跳公共 dispatcher」
│    └─ 这是 Module ABI;建 request ID → 服务名映射表,别判混淆
├─ 代码地址不在模块内存区
│    └─ 先考虑 XIP(指令在 Flash、数据在 RAM)
├─ 线程异常终止
│    ├─ 查是否越界触发了内存保护故障
│    └─ 查栈用量是否把栈顶的入口信息结构算进去
└─ 线程进内核态的行为
     ├─ 非特权模块 → SVC 陷入后才执行 API
     └─ 特权模块 → 直接调用,不产生陷入

工具与验证

  • preamble:逐字段解析(含 properties 位与编译器 ID),并用它定位指令区与数据区
  • API 路径:txm_* 侧调用点与 dispatch 函数、request ID 常量的分布
  • 装载方式:XIP 与拷贝装载的区分(代码地址与模块内存区的关系)
  • 验证:能同时说清「模块是怎么装的(XIP/拷贝)+ API 是怎么进来的(dispatch ID)+ 保护是否开启以及是哪一种」

该平台的坑(汇总)

  • 把 request ID + 公共 dispatcher 当控制流混淆:那是 Module ABI 的正常形态
  • 忘记 preamble 必在第一个地址:找不到它就很难定位指令区/数据区边界
  • 跨版本照搬 preamble 字段偏移:随版本与工具链变化
  • 把 XIP 模块的代码地址当控制流劫持:指令区可以留在 Flash
  • 忽略 properties 位:特权/用户模式与 MPU 开关决定了 API 是否经由 SVC 陷入
  • 算栈用量时漏掉栈顶的线程入口信息结构:会误判栈溢出
  • 把"线程被终止"归因于看门狗:内存保护故障也会终止线程
  • 以为模块只能有一个:模块数量受内存限制,常驻的只有 Module Manager