[v4,6/8] riscv/kprobe: Add code to check if kprobe can be optimized

Message ID 20221106100316.2803176-7-chenguokai17@mails.ucas.ac.cn
State New
Headers
Series Add OPTPROBES feature on RISCV |

Commit Message

Xim Nov. 6, 2022, 10:03 a.m. UTC
  From: Liao Chang <liaochang1@huawei.com>

From: Liao Chang <liaochang1@huawei.com>

This patch add code to check if kprobe can be optimized, regular kprobe
replaces single instruction with EBREAK or C.EBREAK, it just requires
the instrumented instruction support execute out-of-line or simulation,
while optimized kprobe patch AUIPC/JALR pair to do a long jump, it makes
everything more compilated, espeically for kernel that is hybrid RVI and
RVC binary, although AUIPC/JALR just need 8 bytes space, the bytes to
patch are 10 bytes long at worst case to ensure no RVI would be
truncated, so there are four methods to patch optimized kprobe.

  - Replace 2 RVI with AUIPC/JALR.
  - Replace 4 RVC with AUIPC/JALR.
  - Replace 2 RVC and 1 RVI with AUIPC/JALR.
  - Replace 3 RVC and 1 RVI with AUIPC/JALR, and patch C.NOP into last
    two bytes for alignment.

So it has to find out a instruction window large enough to patch
AUIPC/JALR from the address instrumented breakpoint, meanwhile, ensure
no instruction has chance to jump into the range of patched window.

Signed-off-by: Liao Chang <liaochang1@huawei.com>
Co-developed-by: Chen Guokai <chenguokai17@mails.ucas.ac.cn>
Signed-off-by: Chen Guokai <chenguokai17@mails.ucas.ac.cn>
---
 arch/riscv/kernel/probes/opt.c | 99 ++++++++++++++++++++++++++++++++--
 1 file changed, 94 insertions(+), 5 deletions(-)
  

Comments

Björn Töpel Nov. 7, 2022, 4:56 p.m. UTC | #1
Chen Guokai <chenguokai17@mails.ucas.ac.cn> writes:

[...]

> diff --git a/arch/riscv/kernel/probes/opt.c b/arch/riscv/kernel/probes/opt.c
> index 6d23c843832e..876bec539554 100644
> --- a/arch/riscv/kernel/probes/opt.c
> +++ b/arch/riscv/kernel/probes/opt.c

[...]

>  /*
> - * If two free registers can be found at the beginning of both
> - * the start and the end of replaced code, it can be optimized
> - * Also, in-function jumps need to be checked to make sure that
> - * there is no jump to the second instruction to be replaced
> + * The kprobe can be optimized when no in-function jump reaches to the
> + * instructions replaced by optimized jump instructions(AUIPC/JALR).
>   */
>  static bool can_optimize(unsigned long paddr, struct optimized_kprobe *op)
>  {
> -	return false;
> +	int ret;
> +	unsigned long addr, size = 0, offset = 0;
> +	struct kprobe *kp = get_kprobe((kprobe_opcode_t *)paddr);
> +
> +	/*
> +	 * Skip optimization if kprobe has been disarmed or instrumented
> +	 * instruction support XOI.
> +	 */
> +	if (!kp || (riscv_probe_decode_insn(&kp->opcode, NULL) != INSN_GOOD))
> +		return false;
> +
> +	/*
> +	 * Find a instruction window large enough to contain a pair
> +	 * of AUIPC/JALR, and ensure each instruction in this window
> +	 * supports XOI.
> +	 */
> +	ret = search_copied_insn(paddr, op);
> +	if (ret)
> +		return false;
> +
> +	if (!kallsyms_lookup_size_offset(paddr, &size, &offset))
> +		return false;
> +
> +	/* Check there is enough space for relative jump(AUIPC/JALR) */
> +	if (size - offset <= op->optinsn.length)
> +		return false;
> +
> +	/*
> +	 * Decode instructions until function end, check any instruction
> +	 * don't jump into the window used to emit optprobe(AUIPC/JALR).
> +	 */

Don't the fixup tables need to be checked, similar to the x86 code?


Björn
  

Patch

diff --git a/arch/riscv/kernel/probes/opt.c b/arch/riscv/kernel/probes/opt.c
index 6d23c843832e..876bec539554 100644
--- a/arch/riscv/kernel/probes/opt.c
+++ b/arch/riscv/kernel/probes/opt.c
@@ -271,15 +271,103 @@  static void find_free_registers(struct kprobe *kp, struct optimized_kprobe *op,
 	*ra = (kw == 1UL) ? 0 : __builtin_ctzl(kw & ~1UL);
 }
 
+static bool insn_jump_into_range(unsigned long addr, unsigned long start,
+				 unsigned long end)
+{
+	kprobe_opcode_t insn = *(kprobe_opcode_t *)addr;
+	unsigned long target, offset = GET_INSN_LENGTH(insn);
+
+#ifdef CONFIG_RISCV_ISA_C
+	if (offset == RVC_INSN_LEN) {
+		if (riscv_insn_is_c_beqz(insn) || riscv_insn_is_c_bnez(insn))
+			target = addr + rvc_branch_imme(insn);
+		else if (riscv_insn_is_c_jal(insn) || riscv_insn_is_c_j(insn))
+			target = addr + rvc_jal_imme(insn);
+		else
+			target = addr + offset;
+		return (target >= start) && (target < end);
+	}
+#endif
+
+	if (riscv_insn_is_branch(insn))
+		target = addr + rvi_branch_imme(insn);
+	else if (riscv_insn_is_jal(insn))
+		target = addr + rvi_jal_imme(insn);
+	else
+		target = addr + offset;
+	return (target >= start) && (target < end);
+}
+
+static int search_copied_insn(unsigned long paddr, struct optimized_kprobe *op)
+{
+	int i =  1;
+	unsigned long offset = GET_INSN_LENGTH(*(kprobe_opcode_t *)paddr);
+
+	while ((i++ < MAX_COPIED_INSN) && (offset < 2 * RVI_INSN_LEN)) {
+		if (riscv_probe_decode_insn((probe_opcode_t *)paddr + offset,
+					    NULL) != INSN_GOOD)
+			return -1;
+		offset += GET_INSN_LENGTH(*(kprobe_opcode_t *)(paddr + offset));
+	}
+
+	op->optinsn.length = offset;
+	return 0;
+}
+
 /*
- * If two free registers can be found at the beginning of both
- * the start and the end of replaced code, it can be optimized
- * Also, in-function jumps need to be checked to make sure that
- * there is no jump to the second instruction to be replaced
+ * The kprobe can be optimized when no in-function jump reaches to the
+ * instructions replaced by optimized jump instructions(AUIPC/JALR).
  */
 static bool can_optimize(unsigned long paddr, struct optimized_kprobe *op)
 {
-	return false;
+	int ret;
+	unsigned long addr, size = 0, offset = 0;
+	struct kprobe *kp = get_kprobe((kprobe_opcode_t *)paddr);
+
+	/*
+	 * Skip optimization if kprobe has been disarmed or instrumented
+	 * instruction support XOI.
+	 */
+	if (!kp || (riscv_probe_decode_insn(&kp->opcode, NULL) != INSN_GOOD))
+		return false;
+
+	/*
+	 * Find a instruction window large enough to contain a pair
+	 * of AUIPC/JALR, and ensure each instruction in this window
+	 * supports XOI.
+	 */
+	ret = search_copied_insn(paddr, op);
+	if (ret)
+		return false;
+
+	if (!kallsyms_lookup_size_offset(paddr, &size, &offset))
+		return false;
+
+	/* Check there is enough space for relative jump(AUIPC/JALR) */
+	if (size - offset <= op->optinsn.length)
+		return false;
+
+	/*
+	 * Decode instructions until function end, check any instruction
+	 * don't jump into the window used to emit optprobe(AUIPC/JALR).
+	 */
+	addr = paddr - offset;
+	while (addr < paddr) {
+		if (insn_jump_into_range(addr, paddr + RVC_INSN_LEN,
+					 paddr + op->optinsn.length))
+			return false;
+		addr += GET_INSN_LENGTH(*(kprobe_opcode_t *)addr);
+	}
+
+	addr = paddr + op->optinsn.length;
+	while (addr < paddr - offset + size) {
+		if (insn_jump_into_range(addr, paddr + RVC_INSN_LEN,
+					 paddr + op->optinsn.length))
+			return false;
+		addr += GET_INSN_LENGTH(*(kprobe_opcode_t *)addr);
+	}
+
+	return true;
 }
 
 int arch_prepared_optinsn(struct arch_optimized_insn *optinsn)