Files

12 KiB
Raw Permalink Blame History

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 那类);即使存在用共享内存对象创建的区域里,也不能被持有者以外的进程可靠使用

六、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,跨边界有额外开销