# IBM i:单一存储层 / MI 层次 / 程序模型 / 指针与 activation group **两件事决定一切**:所有内容都是**对象**,而一切存储处在**单一地址空间**里。 ``` 主存与辅存不被区分 → 内存 + 磁盘 = 单一 64 位地址空间 对象按【名字】访问,不按硬件地址 ``` 再加上**两套指针**与**两种存储模型**的分野,`object address` 就绝不能当成"当前由 DRAM 支撑的指针"。 **不要套 POSIX 文件/进程模型**——这一层的每一个概念在这里都有对应的、但语义不同的东西。 ## 一、层次:谁在谁上面 ``` MI(Machine Interface) ← 程序与进程在这里是【系统对象】;ILE 支持在此实现 ├─ timi(technology-independent 接口) └─ PASE ← 走另一条路:直接对着 SLIC 的 syscall 接口,绕过 TIMI SLIC / LIC(System Licensed Internal Code) ← 中断处理、异常路由、部分运行时实现 OS(ILE 运行时 / OPM 支持 / 各类服务) ``` - **MI 层跟踪调用流是按"过程(procedure)"而不是按"程序"**——MI 指令里有外部程序调用与"经过程指针调用"两类 - **进程**在 MI 上是系统对象(Process Control Space);**activation group 不是系统对象**,它是 MI 进程对象的一部分 - **PASE 不经过 TIMI**:它对 SLIC 有 syscall 接口,因此跨 PASE↔SLIC 边界的调用有额外开销 **逆向含义**:见到一份代码,先判它在**哪一层**——MI 层的对象、ILE 运行时生成的代码、还是 PASE 里的 AIX 风格二进制,三者的分析工具和语义都不同。 ## 二、程序模型:OPM / EPM / ILE | 模型 | 特征 | |---|---| | **OPM** | 程序对象直接由源成员编译;调用**多在运行时解析**(晚绑定);通常单语言;**不支持模块、过程、服务程序、绑定目录**;**必须运行在默认 activation group** | | **EPM** | 过渡形态(C/400、Pascal 时代),仍然是运行时解析 | | **ILE** | 引入模块、绑定器、优化翻译器;支持多语言混合 | **ILE 的对象三件套**: | 对象 | 说明 | |---|---| | **`*MODULE`** | ILE 编译器的输出;含一个或多个**过程**;**不能直接执行**;被绑定进 `*PGM`/`*SRVPGM` | | **`*PGM`** | 绑定一个或多个模块并指定**入口模块**;**单一入口点**;**bind-by-copy**——过程在编译时解析,开销小,但同一模块被绑进多个程序时内存里会有多份副本 | | **`*SRVPGM`** | 多入口点的可执行单元(每个过程一个入口);**bind-by-reference**——客户端存的是符号链接,**在程序被激活时解析**;**没有入口模块** | - **签名与 binder language**:binder 描述文件管理服务程序**支持的签名与导出列表**(导出符号、程序级别、签名、级别检查);客户端程序激活时,被绑定的服务程序也会被激活,并做**签名匹配** - **binding directory** 保存绑定信息来源 - 模块还可以**导出数据项**:导出方分配并初始化,其他模块导入并共享该存储 **逆向含义**:`*PGM` 里**内含**它的模块;`*SRVPGM` **不内嵌**在调用者里,而是**首次调用时装入内存**——"改了服务程序不需要重编译调用者"正是这个差异的直接后果。 ## 三、activation group:运行期的存储与资源边界 activation group 是为运行一个或多个程序而分配的**运行期工作存储容器**,提供: ``` PSSA(程序静态存储区,全局静态数据) PASA / 调用返回栈(局部变量) 堆存储(动态数据) ``` - 每个 activation group 有自己的控制信息,因此可以有**不同的保护状态、文件使用、提交控制** - **一个 job 有两个默认 activation group**:操作系统用一个,应用用一个;**OPM 程序必须跑在默认组里**;ILE 程序也可以(但会失去模块/过程/服务程序等特性) - 常见选择: | 选择 | 语义 | |---|---| | **命名** | 首个程序创建,后续程序"插进来";**直到 job 结束或显式回收**才消失 | | **`*NEW`** | 系统命名,调用时创建、程序结束时销毁;**服务程序不能用 `*NEW`** | | **`*CALLER`** | 与调用者同组,不新建(服务程序通常的推荐值) | | **`*ENTMOD`** | 由绑定器看入口模块决定 | - **覆盖(override)与文件打开的作用域也在这一层**:作用域可以是 activation group、调用层级或 job;从默认组发出的覆盖通常按调用栈层级生效,从 ILE activation group 发出的按该组生效 **逆向含义**:定位"这个静态变量属于谁、这个文件覆盖为什么没生效",答案在 activation group 而不是在程序里。 ## 四、两套指针与两种存储模型 | | **系统指针(system pointer)** | **空间指针(space pointer)** | |---|---|---| | 指向 | **对象**(带类型信息) | 空间内的偏移 | | 形态 | 16 字节 tagged | 地址 | | 能做什么 | 解析对象、调用、授权检查 | 直接寻址一块空间内的字节 | **两种存储模型并存**: | | **single-level storage** | **teraspace** | |---|---|---| | 范围 | 全局(任何拿到指针的 job 都可访问) | **job 本地**(正常只对拥有它的 job 可见) | | 组成 | 多个 16 MB 单元 | 一个连续地址空间,内含多个独立分配区与未分配空隙 | | 大小 | —— | 约 100 TB 量级 | | mmap | **不支持** | 支持 | | 指针 | —— | **8 字节** | | 跨 job 共享 | 传指针即可 | **只能经共享内存 API**(`shmat`/`mmap` 一类) | - **teraspace 不是空间对象**:它不是系统对象,**不能用系统指针引用**,但可用空间指针在同一 job 内寻址;**只在 job 开始到 job 结束之间存在** - ILE 程序/服务程序可建成 `*TERASPACE` / `*INHERIT` / `*SNGLVL`;**OPM 程序只能用 `*SNGLVL`** - **`*INHERIT` 采用所在 activation group 的存储模型**,并要求 `ACTGRP(*CALLER)` - **teraspace 模型的程序不能被激活进"根程序用单级存储"的 activation group**(含 OPM 默认组);`*TERASPACE` 不能配默认组 - 堆默认跟随存储模型;某些语言可以让堆单独选型(例如把 `malloc` 映到 teraspace 版本) **所以逆向时要问三件事**:这个指针是系统指针还是空间指针?这个地址属于单级存储还是 teraspace?这个程序/服务程序是哪种存储模型?(第三问决定了前两问的合法答案。) ## 五、异常与指针使用限制 - **space addressing violation**:试图在当前空间范围之外操作,或引用 teraspace 内**未分配的页** - 该异常的**数据里带**:与空间关联的对象的**系统指针**、**空间偏移**、以及**空间类别**——空间类别能区分主/次关联空间、隐式进程空间(自动/静态/activation group 堆/常量/句柄堆)、**teraspace**,以及**供 PASE 使用的 teraspace** - **tagged(16 字节)指针的使用限制**(做数据流与取证时要记住): - 指向 teraspace 的 tagged 空间指针**只能由拥有该 teraspace 的进程使用**——被 `fork` 子进程继承、或被别的进程存进共享内存后,**应视为不可用** - **不得存放进由字节流文件映射来的内存**(`mmap`/`shmat` 那类);即使存在用共享内存对象创建的区域里,**也不能被持有者以外的进程可靠使用** ## 六、job / 线程边界 - **teraspace 随 job 生灭**;跨 job 共享必须走共享内存 API - 库解析(例如库列表搜索)是**按线程**的 - ILE 运行时**可能按 activation group 各自维护 `errno`**——要判断值,必须在"同一个线程、同一个 activation group"里取 - **并非所有系统 API 都线程安全**(API 参考逐个标注);把非线程安全 API 经 JNI 调用会得到不可预期的结果 ## 七、可执行材料识别(拿产物时的第一层) | 手段 | 用途 | |---|---| | **DMPOBJ(Dump Object)** | 把对象内容打成 spool file;可用于两份 `*PGM`/`*SRVPGM` 的**差异比对** | | **DBGVIEW(`*LIST`/`*ALL`) 编译** | 借调试 API **提取编译清单**;比二进制比对更省事、更可读 | | 对象压缩(`CPROBJ`/`DCPOBJ`) | `*PGM`/`*SRVPGM`/`*MODULE` 等**可能处于压缩态**;**被使用时系统会自动提供解压版本**——所以"文件看着不像代码"可能只是压缩态 | | 版本/时间戳 | 编译时刻等内嵌结构会让"几乎同时编译的两次产物"**永远不相同**——别据此判被篡改 | **注意**:`*MODULE` 通常建在临时库里,**被绑定进程序后就不再需要**——所以在系统上找不到"当初那个模块对象"是正常的,代码在 `*PGM`/`*SRVPGM` 里面。 ## 决策树 ``` 拿到 IBM i 目标 ├─ 先判层次 │ ├─ MI 层对象 / ILE 运行时产物 / PASE 里的 AIX 风格二进制 │ └─ 三者工具与语义不同 ├─ 拿到的是哪种对象 │ ├─ *PGM(bind-by-copy,内含模块)/*SRVPGM(bind-by-reference,多入口) │ ├─ *MODULE(不可直接执行;可能已被绑定后丢弃) │ └─ 压缩态?先用解压/查看工具,别判"不是代码" ├─ 引用与存储 │ ├─ 系统指针(指向对象)还是空间指针(空间内偏移)? │ ├─ 单级存储(全局、支持传指针共享)还是 teraspace(job 本地、跨 job 需共享内存 API)? │ └─ 程序的存储模型(*TERASPACE/*INHERIT/*SNGLVL)与目标 activation group 是否相容? ├─ 静态数据/文件覆盖行为异常 │ └─ 查 activation group(PSSA/PASA/堆 与覆盖作用域都在这一层) ├─ 异常 │ ├─ space addressing violation → 看异常数据里的系统指针/偏移/空间类别 │ └─ tagged 指针跨 fork / 跨进程使用 → 按不可用处理 └─ 产物比对 ├─ 优先用编译清单(DBGVIEW) └─ DMPOBJ 比对时排除库名/创建时间/PTF 级别等内嵌差异 ``` ## 工具与验证 - 层次与对象:对象类型与所属库;MI 层调用流(按过程追踪) - 程序结构:绑定的模块与入口模块、导出/导入符号、服务程序签名与 binder 语言 - 运行期边界:activation group 的存储区(PSSA/PASA/堆)与覆盖作用域 - 指针与存储:系统指针/空间指针的区分;teraspace 与单级存储的归属 - 验证:能同时说清「这是哪一层的什么对象 + 它的指针是哪种、指向哪种存储 + 它跑在哪个 activation group」 ## 该平台的坑(汇总) - **把对象地址当 DRAM 支撑的指针**:内存与磁盘是一个地址空间 - **用盘号/柱面/扇区理解存储**:物理磁盘被 LIC 隐藏 - **套 POSIX 文件/进程模型**:这里是对象 + 限定名 + 系统指针 - **以为库只是目录**:库本身也是对象、是容器对象 - **混淆两套指针**:系统指针指向对象,空间指针指向空间内偏移 - **混淆两种存储模型**:teraspace 是 job 本地且不是对象;跨 job 只能经共享内存 API - **把 tagged teraspace 指针存进共享内存或让子进程继承后继续用** - **期待在系统上找到"当初那个 `*MODULE`"**:绑定后它通常已不需要 - **把压缩态对象判成损坏/非代码**:使用时会自动解压 - **拿二进制比对结果判篡改**:编译时间戳等内嵌字段必然造成差异;优先用编译清单 - **在错误的 activation group 里读 `errno`**:运行时可能按组维护 - **以为 OPM 程序能进任意 activation group 或用 `*TERASPACE`**:不能 - **把 PASE 与 native 环境当同一套调用边界**:PASE 绕过 TIMI,跨边界有额外开销