2017-10-27 23:25:36 +03:00
/*
* Utility functions for x86 operand and address decoding
*
* Copyright ( C ) Intel Corporation 2017
*/
# include <linux/kernel.h>
# include <linux/string.h>
2017-10-27 23:25:37 +03:00
# include <linux/ratelimit.h>
2017-10-27 23:25:36 +03:00
# include <asm/inat.h>
# include <asm/insn.h>
# include <asm/insn-eval.h>
2017-10-27 23:25:37 +03:00
# undef pr_fmt
# define pr_fmt(fmt) "insn: " fmt
2017-10-27 23:25:36 +03:00
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 :
2017-10-27 23:25:37 +03:00
pr_err_ratelimited ( " invalid register type: %d \n " , type ) ;
return - EINVAL ;
2017-10-27 23:25:36 +03:00
}
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 ;
}