不清空 GHR,如何按历史长度分级阻断 BHI?——Arm US11861368B2 专利解析
不清空 GHR,如何按历史长度分级阻断 BHI?
Arm 分支预测专利解析 · 05
上一篇:预测 taken 的分支实际 not-taken 时,前端如何快速恢复?
这篇文章聚焦分支预测器的安全与性能折中:权限提升后不粗暴清空 GHR 或 ITTAGE,而是根据每张预测表实际使用的历史长度,判断旧权限历史何时已被完全覆盖,再逐级恢复预测能力。
专利:US 11,861,368 B2,Re-enabling Use of Prediction Table After Execution State Switch
申请人/受让人:Arm Limited
申请号:17/752,060;申请日:2022-05-24;授权日:2024-01-02
原始材料:本地 PDF · Google Patents · 公开文本
1. 先把结论说准确
这项方案不是在 EL0 → EL1 时清空全局分支历史(GHR),也不是把 ITTAGE 的训练状态全部作废。它做的是更细的控制:
- 进入更高权限状态时,先禁止 依赖 GHR 的 polymorphic branch target prediction table 参与目标选择;
- 保留 GHR 和各张预测表里的内容;
- 统计切换后有多少个分支真正向 GHR 分配了新历史;
- 一张表使用最近
N个历史项,就等计数达到N后重新启用; - 短历史表先恢复,长历史表后恢复;
- 不依赖 GHR 的 base table,以及本实施例里的 taken/not-taken 方向预测,始终可以工作。
它隔离的不是“表里的 EL0 entry”,而是 EL0 留在 GHR 中、仍可能影响 EL1 lookup 的那一段输入。
还有一个专利阅读上的重要边界:独立权利要求 1 并没有要求“必须不清空 GHR”。它保护的是“权限上升时禁用表,并在历史更新数达到该表的历史长度后重新启用”这一控制关系。不清空 GHR 是说明书强调的性能收益和优选实施方式,不是独立权利要求中的必要限定。
2. 图 3:这不是抽象的 enable bit,而是一条完整的 ITTAGE 选择通路

图 3 给出了专利真正讨论的硬件结构,关键编号如下:
| 编号 | 结构 | 作用 |
|---|---|---|
| 100 | branch history storage / GHR | 按 FIFO 保存最近满足分配条件的分支属性 |
| 102 | prediction control circuitry | 处理 execution-state switch,产生各表 enable |
| 104 | branch counter | 统计切换后造成 GHR 更新的分支数 |
| 120 | Tbase |
只用 PC 派生的 hash 查找,不依赖 GHR |
| 110/112/114 | T0/T1/.../T(M-1) |
分别用越来越长的 GHR0/GHR1/... 与 PC 共同 hash |
| 124/126/128 | tag/context/target | 每个预测 entry 的 tag、上下文标识和目标地址 |
| 144/146/148 | table enable | 控制对应 tagged table 的结果能否参与选择 |
| 140 | prediction selection | 在“已启用且命中”的表中选历史最长者 |
| 150 | target output | 最终的 polymorphic branch target prediction |
2.1 Base table 与 tagged tables 的 lookup 输入不同
Tbase 的 lookup value 130 只由 PC 派生:
Tbase_index/tag = H(PC)
各 tagged table 则分别使用不同长度的全局历史:
T0_lookup = H(PC, GHR0)
T1_lookup = H(PC, GHR1)
...
T(M-1)_lookup = H(PC, GHR(M-1))
len(GHR0) < len(GHR1) < ... < len(GHR(M-1))
因此,权限切换后不是整个 predictor 都失效。Tbase 不读取 GHR,原文明确允许它保持 enabled;真正暂时退出候选集的是 T0...T(M-1)。
2.2 “Disable table”不是擦除 entry
图 3 中 enable 144/146/148 直接送进 prediction selection 140。说明书给出的选择优先级是:
在 enabled && hit 的 tagged tables 中:
选择使用最长 history 的那张表
如果没有 tagged table 同时满足 enabled && hit:
使用 Tbase 的命中结果
如果 Tbase 也 miss:
可退回 BTB,或者按 not-taken 继续顺序取指
所以“禁用”更准确的含义是:表可以仍被读出,entry 也仍然存在,但它的结果不被 selection 140 接纳。 专利不要求为安全而逐项 invalidate,也不要求重训 ITTAGE。
3. 图 4:BHI 如何让 BR_X 命中 BR_Y 的 EL1 entry

图 4 比“攻击者污染分支预测器”这个泛化说法具体得多。
内核 160 中有两条 polymorphic branch:
BR_X(162)靠近从 EL0 进入内核的入口,因此周围带有投机屏障等保护;它的合法目标是T_X0/T_X1;BR_Y(164)离入口较远,没有相同保护;它的T_Y0/T_Y1对 BR_Y 本身是合法目标,但如果从 BR_X 错误到达,就可能成为 gadget。
ITTAGE 中已有两条由 EL1 正常执行训练出的 entry:
entry 166: BR_X | context=EL1 | tag=0xF4 | target=T_X1
entry 168: BR_Y | context=EL1 | tag=0xBC | target=T_Y1
攻击者在 EL0 执行精心安排的分支序列,把 taken/not-taken、target 等分支属性写进 GHR 100,然后通过 supervisor call 进入 EL1。目标是让 BR_X 的 lookup 发生别名:
H(PC_X, malicious_GHR) == tag/index combination of BR_Y entry
于是 BR_X 没有使用自己的 T_X1,而是命中 entry 168,投机跳向 T_Y1。这个目标对 BR_Y 合法,却不是 BR_X 的合法后继。
3.1 为什么 context ID 拦不住
context 126 能阻止:
EL0 lookup -> EL1 entry
EL1 lookup -> EL0 entry
但图 4 的攻击是:
EL1 的 BR_X lookup -> EL1 的 BR_Y entry
entry 168 本来就是 EL1 训练的,context comparison 正常通过。被攻击者控制的是 H(PC, GHR) 的 GHR 部分,从而让一个 EL1 分支别名到另一个 EL1 分支。
这也是 BHI 与早期“直接跨权限训练 target entry”攻击的根本差别:攻击者不必把自己的 EL0 target 塞给内核,而是利用内核已有的合法 target,把内核代码以开发者没有预期的顺序串起来。
3.2 图 4 也说明了攻击限制
这不是任意地址跳转。攻击者仍需同时满足:
- 找到可控的 EL0 分支历史序列;
- 找到能够与 BR_X lookup 别名的内核 BR_Y entry;
T_Y0/T_Y1中存在可利用 gadget;- 错误路径能在被纠正前留下 cache 等微架构痕迹。
专利说明书也承认这种攻击比早期 Spectre 变体更难,但指出它已在实践中被演示。
4. 为什么不采用四种更直接的方案
原文逐一讨论了替代方案,而不是笼统地说“清空性能差”。
4.1 用完整 PC 做精确 tag
如果 entry 保存足够精确的 PC tag,BR_X 就不会命中 BR_Y。但每个 entry 都会变宽,tag comparator 也会变宽,直接增加阵列面积、读出能耗和比较逻辑成本。专利的目标之一就是保留有别名可能、但成本较低的短 hash/partial tag 结构。
4.2 从 lookup hash 中删掉 GHR
这等价于只依赖类似 Tbase 的预测。代价不是一次性冷启动,而是长期丢失 path correlation:同一条间接分支在不同到达路径下可能有不同 target,去掉 GHR 后无法区分这些上下文。
4.3 权限上升时清空 tagged prediction tables
这样会丢掉 EL1 过去积累的 target 状态,而且逐项 invalidation 本身需要时间。内核重新热身的时间远长于一次短 syscall。
4.4 权限上升时清空 GHR
原文给出三个具体损失:
- 很多 supervisor call 在 EL1 只停留很短时间,回到 EL0 时原来的用户态历史仍可能有用;
- EL1 入口分支的方向有时与“从 EL0 的哪条路径发起 syscall”相关,保留 EL0 历史反而能提高 taken/not-taken 准确率;
- 原文称某些典型 workload 中 supervisor call 可能每几千个处理周期就发生一次,反复清空会持续伤害方向预测。
所以这项方案不是声称 EL0 历史“没有价值”,而是把它分成两类用途:
用于高风险 target-table lookup:暂时不可信
用于本实施例的 direction prediction:仍可使用
5. 控制逻辑:阈值必须等于“该表实际读取的历史长度”
权限上升时:
on switch(first_state, second_state):
if privilege(second_state) > privilege(first_state):
branch_counter = 0
disable T0, T1, ..., T(M-1)
keep GHR unchanged
keep Tbase enabled
keep branch-direction prediction enabled // 本实施例
之后,每当一个分支满足 GHR allocation 条件并造成 GHR 更新:
branch_counter += 1
T0_enable = branch_counter >= len(GHR0)
T1_enable = branch_counter >= len(GHR1)
...
T(M-1)_enable = branch_counter >= len(GHR(M-1))
这个阈值不是经验性的安全等待时间,而是一个覆盖证明。假设表 Ti 只读取最近 Ni 个有效历史项,GHR 又按 FIFO 更新,则:
counter < Ni:Ti 的 lookup window 里仍可能包含 EL0 history
counter >= Ni:Ti 的整个 lookup window 都是在切换后分配的 history
因此重新启用条件与 cycle 数、普通指令数、取指 block 数都无关。即使 EL1 已执行了很多 ALU/load/store,只要它们没有向 GHR 分配历史,就不能把旧的 EL0 history 挤出窗口。
6. 图 5:专利给出的 5/10/20 分支阈值

图 5 的例子有三张表:
| 表 | lookup history 长度 | ELx branch count = 6 时的状态 |
|---|---|---|
| T0(110) | 5 branches | enabled |
| T1(112) | 10 branches | disabled |
| T2(114) | 20 branches | disabled |
图中 GHR 尾部已经写入 6 个 EL1 历史项。因此 T0 所读取的最近 5 项必然全部来自 EL1;T1/T2 的窗口仍会向左覆盖到 EL0 区域。
图中的“6 branches”表示计数器 104 已统计到 6 个造成 branch history storage 更新的分支。
7. 图 6:真正保留的性能来自两条始终 enabled 的路径

图 6 把权限切换后的状态分成三类:
7.1 Branch direction predictor 44:持续 enabled
专利实施例认为,只要 target prediction 已被保护,方向预测错误通常只会在软件本来允许的两条路径之间选错:顺序下一条,或该分支的合法 target。它不像错误 target 那样把 BR_X 送到 BR_Y 的 gadget。
这是一个带前提的结论,不应写成“方向预测永远没有 BHI 风险”。说明书后文明确说,同样的计数/重启技术也可以用于其他依赖 GHR 的预测器,包括 TAGE。图 6 表达的是这一个 target-prediction 实施例中的风险划分。
7.2 Tbase 120:持续 enabled
Tbase 只由 PC 派生 lookup value,不读取被 EL0 注入的 GHR,所以无需等待覆盖。Tagged tables 暂时不可用时,ITTAGE 仍有一个低精度 fallback,而不是完全停止 target prediction。
7.3 T0、T1、T2:按历史长度依次恢复
切换瞬间全部 disabled;之后 T0 最先跨过阈值,T1 次之,T2 最后。selection 140 每个阶段都能使用“当前安全且命中的最长历史表”,性能不是在最长历史完全安全后一次性恢复,而是阶梯式恢复。
8. GHR 和 counter 到底统计什么
说明书对 GHR 100 的定义比“一个 taken/not-taken 位串”更宽:
- 它保存最近
N个满足 allocation 条件的分支,而不一定保存所有分支; - allocation 条件可以按 branch type、alignment 等筛选;
- 每项属性可以是 taken/not-taken、target address、二者组合,权利要求 14 还覆盖 branch type;
- 实现可以是 circular buffer,也可以是 shift register,但语义是 FIFO;
- 表的 lookup window 可能从 circular buffer 尾部回绕。
说明书还允许 GHR 为了前端时效性而先写入预测的分支属性,并在 misprediction 时冲掉错误分支及其之后的历史。因此,不能擅自把 claim 中的“branches causing an update”翻译成“已退休分支数”。更准确的实现要求是:counter 必须与当前有效的 GHR allocation 语义一致。
由此还能推出一个必要的实现约束:如果误预测恢复会撤销若干 GHR 项,那么相应计数状态也必须避免把已撤销的历史当成“已覆盖”。否则 counter 可能提前达到阈值,而 lookup window 中实际上仍残留 EL0 历史。专利没有单独展开 counter checkpoint/rollback 电路,但其“窗口全部由切换后历史组成”的安全条件要求二者保持一致。
9. 如果很快返回 EL0,为什么可以立即把表打开
授权后的独立权利要求 1 特别加入了这一条件:如果在 counter 尚未达到阈值时就从 EL1 返回原来的 EL0,prediction control circuitry 重新启用第一预测表。
原因不是 GHR 已经“洗干净”,恰恰相反——GHR 中可能仍保留大量切换前的 EL0 历史。但处理器已经回到原来的低权限状态,不再存在“低权限历史影响更高权限 victim lookup”这一方向的隔离需求,而且旧 EL0 历史对恢复用户态预测很有价值。
可以把完整状态机写成:
EL0 -> EL1:
counter = 0
disable GHR-based target tables
stay in EL1:
for each valid GHR allocation:
counter++
enable each table whose history-length threshold is met
EL1 -> original EL0 before all thresholds are met:
re-enable remaining tables immediately
10. 权限切换不只等于 EL0 → EL1
图 2 和说明书把 execution state 定义为 exception level 与 security state 的组合。示例包括:
- EL0 application → EL1 OS;
- EL1 guest OS → EL2 hypervisor;
- normal world → secure world(如 S-EL0/S-EL1/EL3 monitor);
- 其他“第一状态到更高权限第二状态”的切换。
所以 EL0/EL1 是讲解 BHI 最直观的例子,不是权利要求唯一覆盖的状态对。
11. 权利要求到底保护了哪些层次
把授权文本拆开看,会比一句“Arm 防 BHI 专利”更清楚。
独立权利要求 1:最小控制闭环
核心组合是:
- branch history storage;
- 第一预测表至少基于最近
N个历史项查找; - 从第一状态切到更高权限的第二状态时禁用该表;
- 自切换后造成 history update 的分支数达到
N时重新启用; - 若未达到
N就返回第一状态,也重新启用。
这里既没有限定必须是 ITTAGE,也没有限定 history 只能存 taken/not-taken,更没有把“不清空 GHR”写成 claim element。
从属权利要求 3–6:多表与 tagged-geometric 渐进恢复
- 第二张表使用更长 history,因此更晚恢复;
- 多张 tagged-geometric table 中优先采用“已启用、命中且历史最长”的表;
- 按 history 长度递增顺序逐张恢复。
这部分才对应文章标题中的“分级”。
权利要求 7–9:第一类预测与第二类预测分开处理
- 第一类:branch target address prediction,可被暂时禁用;
- 第二类:taken/not-taken direction prediction,即使读取切换前历史也可保持 enabled。
图 6 就是这组权利要求的可视化。
权利要求 10–16:counter、hash、FIFO 与 polymorphic target
这些从属项覆盖:
- reset 后的 branch counter;
hash(PC, history portion)lookup;- FIFO global history;
- history property 可以依赖 direction、target 或 branch type;
- 一条 branch 在不同 history 下对应多个 target 的 polymorphic prediction。
权利要求 17–18:不只保护分支预测器
第一类预测还可以是:
- data/instruction prefetch prediction;
- data/instruction value prediction。
只要预测表的 lookup 依赖某段 branch history,同样可以在权限上升后按对应历史长度禁用和恢复。ITTAGE 是最具体、最容易理解的实施例,不是保护范围的终点。
12. 从微架构实现角度看,真正需要落地的信号
如果把专利翻译成 RTL 级控制,至少要有以下信息:
privilege_up_switch
return_to_first_state
ghr_allocate
ghr_recovery / valid-history correction
branch_count_since_switch
history_length[T0...T(M-1)]
table_enable[T0...T(M-1)]
一个简化写法是:
if (privilege_up_switch) begin
branch_count <= '0;
table_enable <= '0;
end else if (return_to_first_state) begin
table_enable <= '1;
end else if (ghr_allocate) begin
branch_count <= branch_count + 1'b1;
for (int i = 0; i < NUM_TABLES; i++) begin
if (branch_count + 1'b1 >= HISTORY_LENGTH[i])
table_enable[i] <= 1'b1;
end
end
这段只是帮助理解,不是专利给出的 RTL。真实实现还要处理 counter 饱和、同周期 state switch 与 history allocation 的优先级、预测历史回滚、多个状态域以及不同表是否共享 folded history 等问题。
13. 最容易误读的五点
- 不是清空 ITTAGE。 entry 仍在,只是结果暂时不能进入 selection。
- 不是等待固定 cycle。 等的是足以覆盖该表 lookup window 的有效 GHR allocation 数。
- context tag 仍有用,但它解决的是另一类跨 context entry 污染。 BHI 的 BR_X/BR_Y 都是 EL1 entry。
- 方向预测保持开启是具体实施例的风险选择,不是“所有方向预测绝对安全”的普遍定理。
- 独立权利要求比 ITTAGE 更宽。 多表渐进恢复、target/direction 分离、polymorphic target 都是后续从属层次。
14. 最终理解
这项专利的关键不是“把攻击者留下的历史删除”,而是为每个依赖历史的预测资源建立一个精确的安全时刻:
只有当该资源实际读取的整个历史窗口
都已经由更高权限状态自己的新历史覆盖时,
它才重新获得参与预测的资格。
因此,安全边界跟着每张表的 history length 走:5-history 表等 5 次有效更新,10-history 表等 10 次,20-history 表等 20 次。与此同时,GHR、已训练的 target entries、base predictor 和方向预测的可用状态尽量保留下来。
这就是它相对“清空一切”的价值:不是少做一次 flush,而是把 BHI 防御从全局破坏,缩小成 按 predictor、按 table、按历史窗口长度控制可信度。


