94 Star 261 Fork 91

泰晓科技 / RISCV-Linux

 / 详情

【老师提案】Static Call 技术分析与 RISC-V 移植

进行中
拥有者
创建于  
2022-10-28 00:27

Static Call 技术特点跟 Static Branch 非常类似,可以把 indirect call 动态调整为 direct call,它主要解决的是 retpolines 的性能低效问题,而 retpolines 背后则是臭名昭著的 Meltdown 和 Spectre 漏洞。

目前仅有 x86 和 powerpc 支持,RISC-V 需要做 porting。

参考资料:

  1. Linux static_call
  2. static_call: Add basic static call infrastructure
  3. x86/static_call: Add inline static call implementation for x86-64
  4. Linux Kernel 5.10 Introduces Static Calls to Prevent Speculative Execution Attacks
  5. LWN:利用静态调用来避免retpoline

评论 (11)

falcon 创建了任务
falcon 任务状态待办的 修改为进行中
ForrestNiu 添加协作者ForrestNiu
展开全部操作日志

我先输出一篇《Meltdown 和 Spectre 漏洞分析》

5069540 wuzhangjin 1594531572 falcon 拥有者
回复 ForrestNiu 成员

挺好的,这块可以做成一个系列。

目前已有的文章都过于复杂,可以更简单明了的,通过绘制图表等方式来讲解这个。

falcon 取消协作者ForrestNiu
falcon 负责人设置为ForrestNiu

11月:
1、《Meltdown 和 Spectre 漏洞分析》
2、《retpolines 功能及原理》
12月:
1、《Static Call 功能及原理》
2、Static Call 功能移植
2023年1月:
1、Static Call 功能移植

可以参考相关的代码:

ubuntu@linux-lab-host:~/Develop/cloud-lab/labs/linux-lab/src/linux-stable$ find ./ -name "*static*call*"
./arch/powerpc/include/asm/static_call.h
./arch/powerpc/kernel/static_call.c
./arch/x86/include/asm/static_call.h
./arch/x86/kernel/static_call.c
./include/linux/static_call.h
./include/linux/static_call_types.h
./kernel/static_call.c
./kernel/static_call_inline.c
./tools/include/linux/static_call_types.h

./include/linux/static_call.h 里头有详细的注释,应该是可以通过它来理解原理的,在看这个之前可以同时看看 static branch,也就是我之前分析的 Jump Label 系列。

《retpolines 功能及原理》已完成分析,原理已理解清楚。
后续开始分析 static call 的原理,尝试移植。

From 9bcf50eab84d37993631ef7a5e33585ef950dea9 Mon Sep 17 00:00:00 2001
From: Forrest Niu <forrestniu@foxmail.com>
Date: Sat, 4 Mar 2023 13:51:44 +0800
Subject: [PATCH v2] riscv/static_call: Add static call implementation

Add the riscv static call implementation. For each key, a
permanent trampoline is created which is the destination for all static
calls for the given key.

The trampoline has a direct jump which gets patched by static_call_update()
when the destination function changes.

The "inline" version is under development.

Signed-off-by: Zhangjin Wu <falcon@tinylab.org>
Signed-off-by: Forrest Niu <forrestniu@foxmail.com>
---
 arch/riscv/Kconfig                   |   1 +
 arch/riscv/include/asm/static_call.h |  30 +++++++
 arch/riscv/kernel/Makefile           |   1 +
 arch/riscv/kernel/static_call.c      | 130 +++++++++++++++++++++++++++
 arch/riscv/kernel/vmlinux.lds.S      |   1 +
 5 files changed, 163 insertions(+)
 create mode 100644 arch/riscv/include/asm/static_call.h
 create mode 100644 arch/riscv/kernel/static_call.c

diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index 5b182d1c196c..06f541c6d824 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -115,6 +115,7 @@ config RISCV
 	select HAVE_REGS_AND_STACK_ACCESS_API
 	select HAVE_RSEQ
 	select HAVE_STACKPROTECTOR
+	select HAVE_STATIC_CALL
 	select HAVE_SYSCALL_TRACEPOINTS
 	select IRQ_DOMAIN
 	select IRQ_FORCED_THREADING
diff --git a/arch/riscv/include/asm/static_call.h b/arch/riscv/include/asm/static_call.h
new file mode 100644
index 000000000000..8fa80e0ea16d
--- /dev/null
+++ b/arch/riscv/include/asm/static_call.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_STATIC_CALL_H
+#define _ASM_STATIC_CALL_H
+#include <linux/ftrace.h>
+
+#define __ARCH_DEFINE_STATIC_CALL_TRAMP(name, insns)			\
+	asm(".pushsection .static_call.text, \"ax\"	\n"	\
+	    ".globl " STATIC_CALL_TRAMP_STR(name) "	\n"	\
+	    STATIC_CALL_TRAMP_STR(name) ":		\n"	\
+		insns "					\n"	\
+	    ".type " STATIC_CALL_TRAMP_STR(name) ", @function\n"	\
+	    ".size " STATIC_CALL_TRAMP_STR(name) ", . - " STATIC_CALL_TRAMP_STR(name) "\n" \
+	    ".popsection							\n")
+
+#define ARCH_DEFINE_STATIC_CALL_TRAMP(name, func)			\
+	__ARCH_DEFINE_STATIC_CALL_TRAMP(name, "la t0,"#func";jalr t1,0(t0);")
+
+#define ARCH_DEFINE_STATIC_CALL_NULL_TRAMP(name)			\
+	__ARCH_DEFINE_STATIC_CALL_TRAMP(name, "ret;")
+
+#define ARCH_DEFINE_STATIC_CALL_RET0_TRAMP(name)			\
+	ARCH_DEFINE_STATIC_CALL_TRAMP(name, __static_call_return0;)
+
+#define ARCH_ADD_TRAMP_KEY(name)					\
+	asm(".pushsection .static_call_tramp_key, \"a\"	\n"	\
+	    ".long " STATIC_CALL_TRAMP_STR(name) " - .	\n"	\
+	    ".long " STATIC_CALL_KEY_STR(name) " - .	\n"	\
+	    ".popsection				\n")
+
+#endif /* _ASM_STATIC_CALL_H */
diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile
index 4cf303a779ab..3d86edfedb7e 100644
--- a/arch/riscv/kernel/Makefile
+++ b/arch/riscv/kernel/Makefile
@@ -44,6 +44,7 @@ obj-y	+= setup.o
 obj-y	+= signal.o
 obj-y	+= syscall_table.o
 obj-y	+= sys_riscv.o
+obj-y	+= static_call.o
 obj-y	+= time.o
 obj-y	+= traps.o
 obj-y	+= riscv_ksyms.o
diff --git a/arch/riscv/kernel/static_call.c b/arch/riscv/kernel/static_call.c
new file mode 100644
index 000000000000..31b799ce9d65
--- /dev/null
+++ b/arch/riscv/kernel/static_call.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/static_call.h>
+#include <linux/memory.h>
+#include <linux/bug.h>
+#include <asm/patch.h>
+#include <linux/ftrace.h>
+
+#define RISCV_INSN_JALR 0x00000067U
+#define RISCV_INSN_ADDI 0x00000013U
+#define RISCV_INSN_AUIPC 0x00000017U
+#define AUIPC_T0_MASK	0x00000280U
+#define JALR_T1_T0_MASK	0x00028300U
+
+enum insn_type {
+	CALL = 0, /* site call */
+	NOP = 1,  /* site cond-call */
+	JMP = 2,  /* tramp / site tail-call */
+	RET = 3,  /* tramp / site cond-tail-call */
+};
+
+static void __ref __static_call_transform(void *insn, enum insn_type type, void *func, bool modinit)
+{
+	u32 call[2] = {0};
+	u32 jump_code = (*(u32 *)(insn+sizeof(call[0])) & GENMASK(6, 0));
+
+	switch (type) {
+		case CALL:
+			break;
+		case NOP:
+			break;
+		case JMP:
+			if (jump_code == RISCV_INSN_ADDI) {
+
+				/*
+				 * When the trampoland is initialized, three instructions are as follows:
+				 *
+				 * "auipc t0,imm20"
+				 * "addi t0,imm12"
+				 * "jalr t1,0(t0)"
+				 *
+				 * First, use the "jalr t1,imm12(t0)" replace the "addi t0,imm12"
+				 * To ensure that two instructions are caused in the case of multiple nuclear
+				 * conditions to lead to unknown errors.
+				 */
+
+				make_call_t0(insn, func, call);
+				patch_text_nosync(insn+sizeof(call[0]), &call[1], sizeof(call[1]));
+
+				/*
+				 * After the last dynamic adjustment, the instructions are as follows:
+				 *
+				 * "auipc t0,imm20"
+				 * "jalr t1,imm12(t0)"
+				 * "jalr t1,0(t0)"
+				 *
+				 * The first two instructions have guaranteed that the third instruction will
+				 * not be ordered, so the third instruction can be updated.After the update is completed,
+				 * the second instruction can update to make the third jump instruction take effect.
+				 *
+				 * Use the "auipc t0,imm20" replace "jalr t0,imm12",the third jump instruction will take effect.
+				 */
+
+				make_call_t0(insn+sizeof(call[0]), func, call);
+				patch_text_nosync(insn+sizeof(call), &call[1], sizeof(call[1]));
+				patch_text_nosync(insn+sizeof(call[0]), &call[0], sizeof(call[0]));
+
+			} else if (jump_code == RISCV_INSN_AUIPC) {
+
+				/*
+				 * After the last dynamic adjustment, the instructions are as follows:
+				 *
+				 * "auipc t0,imm20"
+				 * "auipc t0,imm20"
+				 * "jalr t1,imm12(t0)"
+				 *
+				 * The first auipc instruction will not take effect and can be updated easily.
+				 *
+				 * After updating the first auipc instruction, update the second auipc instruction
+				 * to the jalr instruction.
+				 */
+
+				make_call_t0(insn, func, call);
+				patch_text_nosync(insn, &call[0], sizeof(call[0]));
+				patch_text_nosync(insn+sizeof(call[0]), &call[1], sizeof(call[1]));
+
+			} else if (jump_code == RISCV_INSN_JALR) {
+
+				/*
+				 * After the last dynamic adjustment, the instructions are as follows:
+				 *
+				 * "auipc t0,imm20"
+				 * "jalr t1,imm12(t0)"
+				 * "jalr t1,imm12(t0)"
+				 *
+				 * This looks like "jump_code == RISCV_INSN_ADDI".
+				 */
+
+				make_call_t0(insn+sizeof(call[0]), func, call);
+				patch_text_nosync(insn+sizeof(call), &call[1], sizeof(call[1]));
+				patch_text_nosync(insn+sizeof(call[0]), &call[0], sizeof(call[0]));
+			}
+			break;
+		case RET://If func is NULL, the upgrade is not performed
+			break;
+	}
+}
+
+static inline enum insn_type __sc_insn(bool null, bool tail)
+{
+	/*
+	 * Encode the following table without branches:
+	 *
+	 *	tail	null	insn
+	 *	-----+-------+------
+	 *	  0  |   0   |  CALL
+	 *	  0  |   1   |  NOP
+	 *	  1  |   0   |  JMP
+	 *	  1  |   1   |  RET
+	 */
+
+	return 2*tail + null;
+}
+
+void arch_static_call_transform(void *site, void *tramp, void *func, bool tail)
+{
+	mutex_lock(&text_mutex);
+	__static_call_transform(tramp, __sc_insn(!func, true), func, false);
+	mutex_unlock(&text_mutex);
+}
+EXPORT_SYMBOL_GPL(arch_static_call_transform);
diff --git a/arch/riscv/kernel/vmlinux.lds.S b/arch/riscv/kernel/vmlinux.lds.S
index 53a8ad65b255..1b06ac1a511d 100644
--- a/arch/riscv/kernel/vmlinux.lds.S
+++ b/arch/riscv/kernel/vmlinux.lds.S
@@ -48,6 +48,7 @@ SECTIONS
 		ENTRY_TEXT
 		IRQENTRY_TEXT
 		SOFTIRQENTRY_TEXT
+		STATIC_CALL_TEXT
 		_etext = .;
 	}
 
-- 
2.39.2


5069540 wuzhangjin 1594531572 falcon 拥有者
回复 Reset816 成员

最新一版的 Static Call Patch 链接也找一找看看,搜标题应该可以找到。

需要打开 CONFIG_DYNAMIC_FTRACE 配置

登录 后才可以发表评论

状态
负责人
里程碑
Pull Requests
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
开始日期   -   截止日期
-
置顶选项
优先级
参与者(3)
5069540 wuzhangjin 1594531572 5392059 reset12138 1587521141
1
https://gitee.com/tinylab/riscv-linux.git
git@gitee.com:tinylab/riscv-linux.git
tinylab
riscv-linux
RISCV-Linux

搜索帮助

53164aa7 5694891 3bd8fe86 5694891