linux/arch/x86/lib/insn-eval.c

164 lines
3.9 KiB
C

/*
* Utility functions for x86 operand and address decoding
*
* Copyright (C) Intel Corporation 2017
*/
#include <linux/kernel.h>
#include <linux/string.h>
#include <asm/inat.h>
#include <asm/insn.h>
#include <asm/insn-eval.h>
enum reg_type {
REG_TYPE_RM = 0,
REG_TYPE_INDEX,
REG_TYPE_BASE,
};
static int get_reg_offset(struct insn *insn, struct pt_regs *regs,
enum reg_type type)
{
int regno = 0;
static const int regoff[] = {
offsetof(struct pt_regs, ax),
offsetof(struct pt_regs, cx),
offsetof(struct pt_regs, dx),
offsetof(struct pt_regs, bx),
offsetof(struct pt_regs, sp),
offsetof(struct pt_regs, bp),
offsetof(struct pt_regs, si),
offsetof(struct pt_regs, di),
#ifdef CONFIG_X86_64
offsetof(struct pt_regs, r8),
offsetof(struct pt_regs, r9),
offsetof(struct pt_regs, r10),
offsetof(struct pt_regs, r11),
offsetof(struct pt_regs, r12),
offsetof(struct pt_regs, r13),
offsetof(struct pt_regs, r14),
offsetof(struct pt_regs, r15),
#endif
};
int nr_registers = ARRAY_SIZE(regoff);
/*
* Don't possibly decode a 32-bit instructions as
* reading a 64-bit-only register.
*/
if (IS_ENABLED(CONFIG_X86_64) && !insn->x86_64)
nr_registers -= 8;
switch (type) {
case REG_TYPE_RM:
regno = X86_MODRM_RM(insn->modrm.value);
if (X86_REX_B(insn->rex_prefix.value))
regno += 8;
break;
case REG_TYPE_INDEX:
regno = X86_SIB_INDEX(insn->sib.value);
if (X86_REX_X(insn->rex_prefix.value))
regno += 8;
/*
* If ModRM.mod != 3 and SIB.index = 4 the scale*index
* portion of the address computation is null. This is
* true only if REX.X is 0. In such a case, the SIB index
* is used in the address computation.
*/
if (X86_MODRM_MOD(insn->modrm.value) != 3 && regno == 4)
return -EDOM;
break;
case REG_TYPE_BASE:
regno = X86_SIB_BASE(insn->sib.value);
/*
* If ModRM.mod is 0 and SIB.base == 5, the base of the
* register-indirect addressing is 0. In this case, a
* 32-bit displacement follows the SIB byte.
*/
if (!X86_MODRM_MOD(insn->modrm.value) && regno == 5)
return -EDOM;
if (X86_REX_B(insn->rex_prefix.value))
regno += 8;
break;
default:
pr_err("invalid register type");
BUG();
break;
}
if (regno >= nr_registers) {
WARN_ONCE(1, "decoded an instruction with an invalid register");
return -EINVAL;
}
return regoff[regno];
}
/*
* return the address being referenced be instruction
* for rm=3 returning the content of the rm reg
* for rm!=3 calculates the address using SIB and Disp
*/
void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs)
{
int addr_offset, base_offset, indx_offset;
unsigned long linear_addr = -1L;
long eff_addr, base, indx;
insn_byte_t sib;
insn_get_modrm(insn);
insn_get_sib(insn);
sib = insn->sib.value;
if (X86_MODRM_MOD(insn->modrm.value) == 3) {
addr_offset = get_reg_offset(insn, regs, REG_TYPE_RM);
if (addr_offset < 0)
goto out;
eff_addr = regs_get_register(regs, addr_offset);
} else {
if (insn->sib.nbytes) {
/*
* Negative values in the base and index offset means
* an error when decoding the SIB byte. Except -EDOM,
* which means that the registers should not be used
* in the address computation.
*/
base_offset = get_reg_offset(insn, regs, REG_TYPE_BASE);
if (base_offset == -EDOM)
base = 0;
else if (base_offset < 0)
goto out;
else
base = regs_get_register(regs, base_offset);
indx_offset = get_reg_offset(insn, regs, REG_TYPE_INDEX);
if (indx_offset == -EDOM)
indx = 0;
else if (indx_offset < 0)
goto out;
else
indx = regs_get_register(regs, indx_offset);
eff_addr = base + indx * (1 << X86_SIB_SCALE(sib));
} else {
addr_offset = get_reg_offset(insn, regs, REG_TYPE_RM);
if (addr_offset < 0)
goto out;
eff_addr = regs_get_register(regs, addr_offset);
}
eff_addr += insn->displacement.value;
}
linear_addr = (unsigned long)eff_addr;
out:
return (void __user *)linear_addr;
}