12 KiB
12 KiB
IBM i:单一存储层 / MI 层次 / 程序模型 / 指针与 activation group
**两件事决定一切**:所有内容都是**对象**,而一切存储处在**单一地址空间**里。主存与辅存不被区分 → 内存 + 磁盘 = 单一 64 位地址空间
对象按【名字】访问,不按硬件地址
再加上两套指针与两种存储模型的分野,object address 就绝不能当成"当前由 DRAM 支撑的指针"。
不要套 POSIX 文件/进程模型——这一层的每一个概念在这里都有对应的、但语义不同的东西。 </CORE RULE>
一、层次:谁在谁上面
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那类);即使存在用共享内存对象创建的区域里,也不能被持有者以外的进程可靠使用
- 指向 teraspace 的 tagged 空间指针只能由拥有该 teraspace 的进程使用——被
六、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,跨边界有额外开销