Commit ac0ee0a9 authored by Peter Zijlstra's avatar Peter Zijlstra Committed by Ingo Molnar
Browse files

x86/alternatives: Teach text_poke_bp() to patch Jcc.d32 instructions



In order to re-write Jcc.d32 instructions text_poke_bp() needs to be
taught about them.

The biggest hurdle is that the whole machinery is currently made for 5
byte instructions and extending this would grow struct text_poke_loc
which is currently a nice 16 bytes and used in an array.

However, since text_poke_loc contains a full copy of the (s32)
displacement, it is possible to map the Jcc.d32 2 byte opcodes to
Jcc.d8 1 byte opcode for the int3 emulation.

This then leaves the replacement bytes; fudge that by only storing the
last 5 bytes and adding the rule that 'length == 6' instruction will
be prefixed with a 0x0f byte.

Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
Reviewed-by: default avatarMasami Hiramatsu (Google) <mhiramat@kernel.org>
Link: https://lore.kernel.org/r/20230123210607.115718513@infradead.org
parent db7adcfd
Loading
Loading
Loading
Loading
+47 −15
Original line number Diff line number Diff line
@@ -338,6 +338,12 @@ void __init_or_module noinline apply_alternatives(struct alt_instr *start,
	}
}

static inline bool is_jcc32(struct insn *insn)
{
	/* Jcc.d32 second opcode byte is in the range: 0x80-0x8f */
	return insn->opcode.bytes[0] == 0x0f && (insn->opcode.bytes[1] & 0xf0) == 0x80;
}

#if defined(CONFIG_RETPOLINE) && defined(CONFIG_OBJTOOL)

/*
@@ -376,12 +382,6 @@ static int emit_indirect(int op, int reg, u8 *bytes)
	return i;
}

static inline bool is_jcc32(struct insn *insn)
{
	/* Jcc.d32 second opcode byte is in the range: 0x80-0x8f */
	return insn->opcode.bytes[0] == 0x0f && (insn->opcode.bytes[1] & 0xf0) == 0x80;
}

static int emit_call_track_retpoline(void *addr, struct insn *insn, int reg, u8 *bytes)
{
	u8 op = insn->opcode.bytes[0];
@@ -1770,6 +1770,11 @@ void text_poke_sync(void)
	on_each_cpu(do_sync_core, NULL, 1);
}

/*
 * NOTE: crazy scheme to allow patching Jcc.d32 but not increase the size of
 * this thing. When len == 6 everything is prefixed with 0x0f and we map
 * opcode to Jcc.d8, using len to distinguish.
 */
struct text_poke_loc {
	/* addr := _stext + rel_addr */
	s32 rel_addr;
@@ -1891,6 +1896,10 @@ noinstr int poke_int3_handler(struct pt_regs *regs)
		int3_emulate_jmp(regs, (long)ip + tp->disp);
		break;

	case 0x70 ... 0x7f: /* Jcc */
		int3_emulate_jcc(regs, tp->opcode & 0xf, (long)ip, tp->disp);
		break;

	default:
		BUG();
	}
@@ -1964,16 +1973,26 @@ static void text_poke_bp_batch(struct text_poke_loc *tp, unsigned int nr_entries
	 * Second step: update all but the first byte of the patched range.
	 */
	for (do_sync = 0, i = 0; i < nr_entries; i++) {
		u8 old[POKE_MAX_OPCODE_SIZE] = { tp[i].old, };
		u8 old[POKE_MAX_OPCODE_SIZE+1] = { tp[i].old, };
		u8 _new[POKE_MAX_OPCODE_SIZE+1];
		const u8 *new = tp[i].text;
		int len = tp[i].len;

		if (len - INT3_INSN_SIZE > 0) {
			memcpy(old + INT3_INSN_SIZE,
			       text_poke_addr(&tp[i]) + INT3_INSN_SIZE,
			       len - INT3_INSN_SIZE);

			if (len == 6) {
				_new[0] = 0x0f;
				memcpy(_new + 1, new, 5);
				new = _new;
			}

			text_poke(text_poke_addr(&tp[i]) + INT3_INSN_SIZE,
				  (const char *)tp[i].text + INT3_INSN_SIZE,
				  new + INT3_INSN_SIZE,
				  len - INT3_INSN_SIZE);

			do_sync++;
		}

@@ -2001,8 +2020,7 @@ static void text_poke_bp_batch(struct text_poke_loc *tp, unsigned int nr_entries
		 * The old instruction is recorded so that the event can be
		 * processed forwards or backwards.
		 */
		perf_event_text_poke(text_poke_addr(&tp[i]), old, len,
				     tp[i].text, len);
		perf_event_text_poke(text_poke_addr(&tp[i]), old, len, new, len);
	}

	if (do_sync) {
@@ -2019,10 +2037,15 @@ static void text_poke_bp_batch(struct text_poke_loc *tp, unsigned int nr_entries
	 * replacing opcode.
	 */
	for (do_sync = 0, i = 0; i < nr_entries; i++) {
		if (tp[i].text[0] == INT3_INSN_OPCODE)
		u8 byte = tp[i].text[0];

		if (tp[i].len == 6)
			byte = 0x0f;

		if (byte == INT3_INSN_OPCODE)
			continue;

		text_poke(text_poke_addr(&tp[i]), tp[i].text, INT3_INSN_SIZE);
		text_poke(text_poke_addr(&tp[i]), &byte, INT3_INSN_SIZE);
		do_sync++;
	}

@@ -2040,9 +2063,11 @@ static void text_poke_loc_init(struct text_poke_loc *tp, void *addr,
			       const void *opcode, size_t len, const void *emulate)
{
	struct insn insn;
	int ret, i;
	int ret, i = 0;

	memcpy((void *)tp->text, opcode, len);
	if (len == 6)
		i = 1;
	memcpy((void *)tp->text, opcode+i, len-i);
	if (!emulate)
		emulate = opcode;

@@ -2053,6 +2078,13 @@ static void text_poke_loc_init(struct text_poke_loc *tp, void *addr,
	tp->len = len;
	tp->opcode = insn.opcode.bytes[0];

	if (is_jcc32(&insn)) {
		/*
		 * Map Jcc.d32 onto Jcc.d8 and use len to distinguish.
		 */
		tp->opcode = insn.opcode.bytes[1] - 0x10;
	}

	switch (tp->opcode) {
	case RET_INSN_OPCODE:
	case JMP32_INSN_OPCODE:
@@ -2069,7 +2101,6 @@ static void text_poke_loc_init(struct text_poke_loc *tp, void *addr,
		BUG_ON(len != insn.length);
	}


	switch (tp->opcode) {
	case INT3_INSN_OPCODE:
	case RET_INSN_OPCODE:
@@ -2078,6 +2109,7 @@ static void text_poke_loc_init(struct text_poke_loc *tp, void *addr,
	case CALL_INSN_OPCODE:
	case JMP32_INSN_OPCODE:
	case JMP8_INSN_OPCODE:
	case 0x70 ... 0x7f: /* Jcc */
		tp->disp = insn.immediate.value;
		break;