# API 与数据结构

## 公共约定

- `target`：正整数 PID、有效 `process` 对象，或表示当前进程的 `null`。0、负数、字符串 PID、空表均为错误，不会回退到当前进程。
- 地址、大小用数值表示，单位**字节**；内部统一成无符号 x86 地址。大于等于 `0x80000000` 的有效地址不会以负数输出。
- 地址范围采用 **[startAddress, endAddress)**：左端包含，右端不包含。可指定非页对齐端点；统计会裁剪到精确范围。
- 可选参数表位于最后；只需要当前进程且要传选项时使用 `fn(,{...})`，不要把选项表放到目标参数的位置。
- 参数或采集前置条件错误返回 `null,err`，正常结果是纯数据表。扫描中断返回部分快照/诊断，必须检查 `complete`。

## snapshot(target, options)

采集全部区域状态，不按可读性过滤。返回 `kind="snapshot"`。

| options 字段 | 默认 | 说明 |
| --- | --- | --- |
| `startAddress` | 最低应用地址 | 必须处于目标应用范围，通常为 64 KiB |
| `endAddress` | 探测上界 | 不包含该地址；不硬编码所有目标为 2 GiB |
| `maxRegions` | 131072 | 1～1048576，限制区域记录数量 |
| `timeoutMs` | 5000 | 0～3600000；0 关闭区域扫描时间限制 |
| `resolveNames` | false | 查询映像/映射路径；失败只记计数，不改变基础统计 |
| `counters` | true | 附加 `process.usage` 进程计数器 |
| `threadCount` | false | 附加 OS 线程数；不是根据保护页推断 |

超时是区域循环的检查预算，不中断正在执行的系统调用，不包含前置架构/范围探测及可选名称、计数器查询。`durationMs` 记录区域扫描时长。

### 快照字段

| 字段 | 含义 |
| --- | --- |
| `schemaVersion` / `libraryVersion` | 数据协议版本 1 / 库版本 |
| `target` | `pid`, `architecture`, `creationTimeKey`, `largeAddressAware`，可选 `name`, `imagePath` |
| `creationTimeKey` | 实际位于 `target`；进程创建 FILETIME 的 16 位十六进制字符串，防止 64 位时间转浮点损失 |
| `collectorPid` | 执行查询的进程 PID |
| `capturedAt` | 扫描开始的 Unix 毫秒时间戳 |
| `pageSize` / `allocationGranularity` | 系统页大小 / 新预留对齐粒度 |
| `scanRange` | 请求与目标范围，见下文 |
| `observedEndAddress` | 连续已扫描前缀的右端点，不包含 |
| `complete` | 是否覆盖整个请求范围，且扫描结束检查时目标仍存活；不是原子性承诺 |
| `stopReason` | `completed`, `maxRegions`, `timeout`, `queryFailed`, `invalidRegion`, `targetExited` |
| `error` | 查询失败时的 `address`, `win32`, `message`，否则通常不存在 |
| `regions` | 按地址递增的完整已观察区域数组，未按展示上限截断 |
| `warnings` | 如自观测干扰、可选计数器不可用 |
| `nameResolution` | 开启名称查询才有：`requested`, `resolved`, `failed`, `pathFormat` |
| `counters` / `threadCount` | 可选附加数据；采样时刻不保证与每个区域相同 |

`scanRange` 包含 `startAddress`, `endAddress`, `systemStartAddress`, `systemEndAddress`, `fullAddressSpace`。`system*` 是**本次为目标确定的应用查询范围**，不是整个机器的物理地址范围。

### regions 的元素

- `baseAddress`, `endAddress`, `size`：本次观察范围内的区域。窗口裁剪时 `clipped=true`。
- `state`：`free`, `reserved`, `committed`, `unknown`；`stateCode` 保留原始数值。
- `kind`：`free`, `private`, `mapped`, `image`, `unknown`。
- 非空闲区才有 `allocationBase`, `allocationProtect`, `typeCode`。
- 已提交区才有 `protect`, `protectText`；`guard` 只依据已提交区域的 `PAGE_GUARD`。
- 可选 `mappedPath`：NT 设备路径，例如 `\Device\HarddiskVolume...`；不伪装成 DOS 盘符路径。

### counters

`workingSetBytes`, `privateUsageBytes`, `pagefileUsageBytes`, `peakWorkingSetBytes`, `sampledAt`。这些是进程级计数器，单独显示；不与区域枚举结果混为同一种占用。

## diagnose(target, options)

等价于采集 `snapshot` 后调用 `analyze`。接受上面采集选项及下面分析选项。默认只输出精简诊断，不附带几千条原始区域；要保存区域需直接调用 `snapshot`。

## analyze(snapshot, options)

完全离线，不重新查询进程，不修改输入。检查快照版本、身份、几何连续性和完整性；损坏的间隙、重叠、错误 size 不会被默默统计。

| options 字段 | 默认 | 说明 |
| --- | --- | --- |
| `top` | 8 | 每个排行榜/候选列表最多展示的项数，0～1000；0 不展示明细但保留总数 |
| `requestBytes` | 不评估 | 1～4294967296 的整数字节数；模拟新预留连续虚拟地址空间的大小条件 |
| `alignment` | 快照分配粒度 | 必须为 `allocationGranularity` 的正整数倍 |

### summary

- `scannedBytes`, `freeBytes`, `committedBytes`, `reservedBytes`, `unknownBytes`。
- `largestFreeBytes`、存在空闲时的 `largestFreeAddress`；相邻空闲片段合并后统计。
- `regionCount`, `freeRegionCount`, `allocationCount`。
- `guardedPrivateAllocations`, `guardedPrivateBytes`, `guardBytes`。
- `executableWritableBytes`：具有 EXECUTE_READWRITE / EXECUTE_WRITECOPY 的已提交字节，仅是属性计数，不是恶意性判断。
- `byKind.private/image/mapped/unknown`：各自的 `virtualBytes`, `committedBytes`, `reservedBytes`, `regionCount`。

`reservedBytes` **仅**表示 MEM_RESERVE，不把已提交字节重复包含进去。对于已识别的状态，`scannedBytes = freeBytes + committedBytes + reservedBytes + unknownBytes`。

### rankings

`largestAllocations`, `mostCommitted`, `mostReserved`, `largestFree`, `guardedPrivate`, `repeatedSizes`。

每个榜为 `{items, total, displayTruncated}`，截断只影响展示。分配按 `AllocationBase` 聚合，大小仅涵盖本次观察范围。`partial=true` 表示可能没有覆盖完整分配，不能将其当作完整分配大小。

分配行含 `allocationBase`, `baseAddress`, `endAddress`, `size`, `committedBytes`, `reservedBytes`, `guardBytes`, `regionCount`, `kind`, `partial` 以及可选路径。重复尺寸按**原始整数字节数 + 类型 + 是否部分范围**分组，不用四舍五入后的 MiB 字符串合并。

### request

- `requestedBytes`：原始请求。
- `roundedBytes`：向上取整到页大小。
- `alignment`, `largestAlignedFreeBytes`, `candidateRegionCount`, `candidates`, `displayTruncated`。
- `status="candidateObserved"`：在已观察部分发现候选，仍不保证分配成功。
- `status="notFoundInRange"`：完整请求范围内未发现候选。
- `status="unknownIncomplete"`：未发现候选，但扫描不完整，不能作否定判断。
- 未发现候选的 `reason`：`insufficientTotalFree`, `fragmentedFreeSpace`, `alignmentLimited`。
- `allocationGuaranteed` 始终为 false。

候选数是**能容纳一次请求的空闲区域数**，不是所有可能起始地址的数量，也不是能够同时进行的分配次数。不尝试分配，不预测堆分配器内部行为，不检查大页、配额或系统提交限额。

### findings

每项包含 `code`, `severity`, `message`, `evidence`, `advice`。目前代码：

`SCAN_INCOMPLETE`, `CUSTOM_RANGE`, `UNKNOWN_STATE`, `GUARDED_PRIVATE`, `REQUEST_UNDETERMINED`, `REQUEST_CANDIDATE_OBSERVED`, `REQUEST_NO_CANDIDATE`。

没有任意的“健康分数”或“泄漏概率”。不指定请求大小时，主要提供事实统计与边界提示，不擅自给碎片程度设置报警阈值。

## diff(before, after, options)

默认 `options.top=20`，0～1000。

要求两份 snapshot：

- `complete=true`。
- 同一 PID、创建时间及架构。
- 同一请求地址范围、页大小及分配粒度。
- after 的采样开始时间不早于 before。

逐区间对齐比较，不将 API 返回边界拆分误认为新增分配。不完整、不同范围或不同进程实例直接返回 `null,err`。

返回 `kind="diff"`：

- `delta`：各项 summary 的 `after - before`，包含按类型的增量。
- `counterDelta`：两边都有计数器时才有。
- `changedBytes`, `changeCount`：全范围变化汇总。
- `changes`：按变化字节降序排列的前 top 项，含地址、大小及 `before` / `after` 属性。
- `transitions`：状态转换的字节数；保护变化可能是 `committed->committed`。
- `displayTruncated`：仅 changes 展示是否截断。

同地址释放后重用、且两次属性完全相同，不能被这种快照对比识别为新分配事件。

## query(target, address, options)

`address` 为 0～0xFFFFFFFF 的数值或指针。可用 `options.resolveNames=true`。

返回 `queriedAddress`, `region`, `offsetInReturnedRegion`, `bytesUntilRegionEnd`，非空闲区还有 `offsetFromAllocation`。

**注意 VirtualQueryEx 的语义：从所查询地址对应的页开始向后返回同属性范围。** `region.baseAddress` 不一定是整个同属性区域的最早地址，`region.size` 也不一定是完整分配大小。需要完整布局应使用 snapshot。

## format(result, options)

接受 snapshot、diagnosis、diff、query，返回简短的 CRLF 文本。仅当输入 snapshot 时将 options 传给 analyze；其他结果按已有内容格式化，不重新采集或改变 top。

这不是 JSON 编码器；保存完整结构请使用标准 `JSON.save` / `JSON.stringify`。
