LWN:利用静态调用来避免retpoline

关注了就能看到更多这么棒的文章哦~

Avoiding retpolines with static calls

By Jonathan Corbet
March 26, 2020

原文来自:https://lwn.net/Articles/815908/

主译:https://www.deepl.com/translator

2018年1月是内核社区的悲哀时刻,因为Meltdown 和 Spectre 漏洞终于被披露出来了。为了解决这两个问题大家做了许多工作,可是无可避免地在很多方面损害了内核性能。其中的一个workaround名为retpolines,目前仍在给大家带来不少麻烦。开发者们在想尽办法来避免间接调用(indirect call),因为现在间接调用必须通过retpolines来实现。不过,在某些情况下,也许有办法避免retpolines,这样可以把我们损失的大部分性能找回来。。经过漫长的酝酿期后,"静态调用(static call) "机制终于接近于可以合并入upstream的阶段。

间接调用indirect call发生在编译时不知道要调用的函数的地址的情况,需要将该地址存储在一个指针变量中,在运行时使用。事实证明,这些间接调用很容易被speculative-executation攻击方式所利用。Retpolines通过将间接调用转化为一个相当复杂(而且开销很大)的代码序列(code sequence)来防御这些攻击,使其无法被投机地(speculatively)执行。

Retpolines解决了这个安全问题,但它也同时拖慢了内核。所以开发者们一直热衷于寻找避免使用retpoline的方法。人们已经尝试了许多方法;其中的一些方法,LWN在2018年底已经介绍过了(https://lwn.net/Articles/774743/  )。虽然其中的一些技术已经被合入mainline,但是静态调用仍然没能被合入。Peter Zijlstra发布了一组patch set来实现静态调用,当然其中也包含了其他一些开发者的工作,特别是Josh Poimboeuf,因为最初的静态调用实现就是他发出来的。

间接调用是从可写内存中的一个位置工作的,在这个位置上可以找到跳转的目的地。改变调用的目的地就是在该位置存储一个新的地址。而静态调用则使用可执行内存中的一个位置,其中包含指向目标函数的跳转指令。实际上执行静态调用需要 "调用 "到这个特殊的位置,它将立即跳转到真正的目标。可以说,静态调用位置就是一个经典的代码跳转位置。由于这两种跳转都是直接的--目标地址直接在可执行代码本身就能找到,所以不需要retpolines,执行速度很快。

在使用静态调用之前必须先声明,可以用这两个宏来声明:

    #include <linux/static_call.h>

    DEFINE_STATIC_CALL(name, target);
    DECLARE_STATIC_CALL(name, target);

DEFINE_STATIC_CALL() 会利用name这个参数来创建一个新的静态调用,初始会指向target()这个函数。而DECLARE_STATIC_CALL()则是声明在其他某个地方已经定义了这个静态调用,在这种情况下,target()只是用于对函数指针进行类型检查的。

真正调用一个静态调用是用这样来做的:

    static_call(name)(args...);

其中name指出了static call的名称。这个函数会经过一个trampoline跳转到target函数去,如果该函数有返回值的话,static_call()也将返回同样的值。

可以调用这个API来改变静态调用的目标函数:

    static_call_update(name, target2);

其中target2()是静态调用的新的目标函数。改变静态调用的目标函数的话,需要对正在运行的kernel的代码进行修补(patching),这个操作的开销很大。这意味着静态调用只适用于目标函数很少改变的场景。

在这组patch set中可以看到一个非常适合这个要求的场景:tracepoints。激活一个tracepoint本身就需要代码补丁(code patching)操作。tracepoint激活后,当每次tracepoint命中时,内核会通过遍历一个跟此tracepoint绑定起来的linked list中所有的回调函数。绝大多数情况下,list中只有一个函数。这组patch set就优化了tracepoint如果只对应单个函数的情况。由于 tracepoints 的设计目标之一就是尽可能地减少开销,所以在tracepoint场景使用静态调用是非常有意义的。

这个patch set还针对上一版本又额外实现了一个新的优化。通过trampoline跳转要比使用retpoline快得多,但仍然会引入一次额外的跳转。因此,这个patch会把静态调用的目标地址直接存储到calling site(调用地点)中,就彻底不需要trampoline了。这种做法需要对多处调用地点进行改动,不过绝大多数的静态调用中,只有很少数地方会调用它。此外这个功能还需要objtool工具能在kernel build过程中确定这些call site(调用位置)。

这项工作,最终可以让使用tracepoints时由于Spectre mitigation而引入的额外性能损失大幅降低——从超过4%的性能影响下降到1.6%左右。这组patch set已经经过了多次修改,底层text-patching(代码修补)的代码也进行了一些改进,看起来已经一切就绪了。静态调用很可能在不久的将来就会合入upstream。

译者注:

1. retpoline是Google创造的一种软件方式来预防speculative exploit。简单来说就是创建一组无限循环来避免CPU预测执行。参见https://support.google.com/faqs/answer/7625886

2. trampoline直译为蹦床,简单来说就是一个跳转点。

全文完

LWN文章遵循CC BY-SA 4.0许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注LWN深度文章以及开源社区的各种新近言论~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值