blob: df9418c433568255c6f3006a0417f338d34989ed [file] [log] [blame]
Ricardo Neri32542ee2017-10-27 13:25:36 -07001/*
2 * Utility functions for x86 operand and address decoding
3 *
4 * Copyright (C) Intel Corporation 2017
5 */
6#include <linux/kernel.h>
7#include <linux/string.h>
8#include <asm/inat.h>
9#include <asm/insn.h>
10#include <asm/insn-eval.h>
11
12enum reg_type {
13 REG_TYPE_RM = 0,
14 REG_TYPE_INDEX,
15 REG_TYPE_BASE,
16};
17
18static int get_reg_offset(struct insn *insn, struct pt_regs *regs,
19 enum reg_type type)
20{
21 int regno = 0;
22
23 static const int regoff[] = {
24 offsetof(struct pt_regs, ax),
25 offsetof(struct pt_regs, cx),
26 offsetof(struct pt_regs, dx),
27 offsetof(struct pt_regs, bx),
28 offsetof(struct pt_regs, sp),
29 offsetof(struct pt_regs, bp),
30 offsetof(struct pt_regs, si),
31 offsetof(struct pt_regs, di),
32#ifdef CONFIG_X86_64
33 offsetof(struct pt_regs, r8),
34 offsetof(struct pt_regs, r9),
35 offsetof(struct pt_regs, r10),
36 offsetof(struct pt_regs, r11),
37 offsetof(struct pt_regs, r12),
38 offsetof(struct pt_regs, r13),
39 offsetof(struct pt_regs, r14),
40 offsetof(struct pt_regs, r15),
41#endif
42 };
43 int nr_registers = ARRAY_SIZE(regoff);
44 /*
45 * Don't possibly decode a 32-bit instructions as
46 * reading a 64-bit-only register.
47 */
48 if (IS_ENABLED(CONFIG_X86_64) && !insn->x86_64)
49 nr_registers -= 8;
50
51 switch (type) {
52 case REG_TYPE_RM:
53 regno = X86_MODRM_RM(insn->modrm.value);
54 if (X86_REX_B(insn->rex_prefix.value))
55 regno += 8;
56 break;
57
58 case REG_TYPE_INDEX:
59 regno = X86_SIB_INDEX(insn->sib.value);
60 if (X86_REX_X(insn->rex_prefix.value))
61 regno += 8;
62
63 /*
64 * If ModRM.mod != 3 and SIB.index = 4 the scale*index
65 * portion of the address computation is null. This is
66 * true only if REX.X is 0. In such a case, the SIB index
67 * is used in the address computation.
68 */
69 if (X86_MODRM_MOD(insn->modrm.value) != 3 && regno == 4)
70 return -EDOM;
71 break;
72
73 case REG_TYPE_BASE:
74 regno = X86_SIB_BASE(insn->sib.value);
75 /*
76 * If ModRM.mod is 0 and SIB.base == 5, the base of the
77 * register-indirect addressing is 0. In this case, a
78 * 32-bit displacement follows the SIB byte.
79 */
80 if (!X86_MODRM_MOD(insn->modrm.value) && regno == 5)
81 return -EDOM;
82
83 if (X86_REX_B(insn->rex_prefix.value))
84 regno += 8;
85 break;
86
87 default:
88 pr_err("invalid register type");
89 BUG();
90 break;
91 }
92
93 if (regno >= nr_registers) {
94 WARN_ONCE(1, "decoded an instruction with an invalid register");
95 return -EINVAL;
96 }
97 return regoff[regno];
98}
99
100/*
101 * return the address being referenced be instruction
102 * for rm=3 returning the content of the rm reg
103 * for rm!=3 calculates the address using SIB and Disp
104 */
105void __user *insn_get_addr_ref(struct insn *insn, struct pt_regs *regs)
106{
107 int addr_offset, base_offset, indx_offset;
108 unsigned long linear_addr = -1L;
109 long eff_addr, base, indx;
110 insn_byte_t sib;
111
112 insn_get_modrm(insn);
113 insn_get_sib(insn);
114 sib = insn->sib.value;
115
116 if (X86_MODRM_MOD(insn->modrm.value) == 3) {
117 addr_offset = get_reg_offset(insn, regs, REG_TYPE_RM);
118 if (addr_offset < 0)
119 goto out;
120
121 eff_addr = regs_get_register(regs, addr_offset);
122 } else {
123 if (insn->sib.nbytes) {
124 /*
125 * Negative values in the base and index offset means
126 * an error when decoding the SIB byte. Except -EDOM,
127 * which means that the registers should not be used
128 * in the address computation.
129 */
130 base_offset = get_reg_offset(insn, regs, REG_TYPE_BASE);
131 if (base_offset == -EDOM)
132 base = 0;
133 else if (base_offset < 0)
134 goto out;
135 else
136 base = regs_get_register(regs, base_offset);
137
138 indx_offset = get_reg_offset(insn, regs, REG_TYPE_INDEX);
139
140 if (indx_offset == -EDOM)
141 indx = 0;
142 else if (indx_offset < 0)
143 goto out;
144 else
145 indx = regs_get_register(regs, indx_offset);
146
147 eff_addr = base + indx * (1 << X86_SIB_SCALE(sib));
148 } else {
149 addr_offset = get_reg_offset(insn, regs, REG_TYPE_RM);
150 if (addr_offset < 0)
151 goto out;
152
153 eff_addr = regs_get_register(regs, addr_offset);
154 }
155
156 eff_addr += insn->displacement.value;
157 }
158
159 linear_addr = (unsigned long)eff_addr;
160
161out:
162 return (void __user *)linear_addr;
163}