1 /* SPDX-License-Identifier: GPL-2.0-only */
2 #include <arch/registers.h>
3 #include <arch/breakpoint.h>
4 #include <types.h>
5
6 #define DEBUG_REGISTER_COUNT 4
7
8 /* Each enable field is 2 bits and starts at bit 0 */
9 #define DEBUG_CTRL_ENABLE_SHIFT(index) (2 * (index))
10 #define DEBUG_CTRL_ENABLE_MASK(index) (0x3 << DEBUG_CTRL_ENABLE_SHIFT(index))
11 #define DEBUG_CTRL_ENABLE(index, enable) ((enable) << DEBUG_CTRL_ENABLE_SHIFT(index))
12
13 /* Each breakpoint has a length and type, each is two bits and start at bit 16 */
14 #define DEBUG_CTRL_LT_SHIFT(index) (4 * (index) + 16)
15 #define DEBUG_CTRL_LT_MASK(index) (0xf << DEBUG_CTRL_LT_SHIFT(index))
16 #define DEBUG_CTRL_LT(index, len, type) ((((len) << 2 | (type))) << DEBUG_CTRL_LT_SHIFT(index))
17
18 /* Each field is one bit, starting at bit 0 */
19 #define DEBUG_STATUS_BP_HIT_MASK(index) (1 << (index))
20 #define DEBUG_STATUS_GET_BP_HIT(index, value) \
21 (((value) & DEBUG_STATUS_BP_HIT_MASK(index)) >> (index))
22
23 /* Breakpoint lengths values */
24 #define DEBUG_CTRL_LEN_1 0x0
25 #define DEBUG_CTRL_LEN_2 0x1
26 #define DEBUG_CTRL_LEN_8 0x2
27 #define DEBUG_CTRL_LEN_4 0x3
28
29 /* Breakpoint enable values */
30 #define DEBUG_CTRL_ENABLE_LOCAL 0x1
31 #define DEBUG_CTRL_ENABLE_GLOBAL 0x2
32
33 /* eflags/rflags bit to continue execution after hitting an instruction breakpoint */
34 #define FLAGS_RESUME (1 << 16)
35
36 struct breakpoint {
37 bool allocated;
38 enum breakpoint_type type;
39 breakpoint_handler handler;
40 };
41
42 static struct breakpoint breakpoints[DEBUG_REGISTER_COUNT];
43
debug_write_addr_reg(int index,uintptr_t value)44 static inline bool debug_write_addr_reg(int index, uintptr_t value)
45 {
46 switch (index) {
47 case 0:
48 asm("mov %0, %%dr0" ::"r"(value));
49 break;
50
51 case 1:
52 asm("mov %0, %%dr1" ::"r"(value));
53 break;
54
55 case 2:
56 asm("mov %0, %%dr2" ::"r"(value));
57 break;
58
59 case 3:
60 asm("mov %0, %%dr3" ::"r"(value));
61 break;
62
63 default:
64 return false;
65 }
66
67 return true;
68 }
69
debug_read_status(void)70 static inline uintptr_t debug_read_status(void)
71 {
72 uintptr_t ret = 0;
73
74 asm("mov %%dr6, %0" : "=r"(ret));
75 return ret;
76 }
77
debug_write_status(uintptr_t value)78 static inline void debug_write_status(uintptr_t value)
79 {
80 asm("mov %0, %%dr6" ::"r"(value));
81 }
82
debug_read_control(void)83 static inline uintptr_t debug_read_control(void)
84 {
85 uintptr_t ret = 0;
86
87 asm("mov %%dr7, %0" : "=r"(ret));
88 return ret;
89 }
90
debug_write_control(uintptr_t value)91 static inline void debug_write_control(uintptr_t value)
92 {
93 asm("mov %0, %%dr7" ::"r"(value));
94 }
95
allocate_breakpoint(struct breakpoint_handle * out_handle,enum breakpoint_type type)96 static enum breakpoint_result allocate_breakpoint(struct breakpoint_handle *out_handle,
97 enum breakpoint_type type)
98 {
99 for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
100 if (breakpoints[i].allocated)
101 continue;
102
103 breakpoints[i].allocated = true;
104 breakpoints[i].handler = NULL;
105 breakpoints[i].type = type;
106 out_handle->bp = i;
107 return BREAKPOINT_RES_OK;
108 }
109
110 return BREAKPOINT_RES_NONE_AVAILABLE;
111 }
112
validate_handle(struct breakpoint_handle handle)113 static enum breakpoint_result validate_handle(struct breakpoint_handle handle)
114 {
115 int bp = handle.bp;
116
117 if (bp < 0 || bp >= DEBUG_REGISTER_COUNT || !breakpoints[bp].allocated)
118 return BREAKPOINT_RES_INVALID_HANDLE;
119
120 return BREAKPOINT_RES_OK;
121 }
122
breakpoint_create_instruction(struct breakpoint_handle * out_handle,void * virt_addr)123 enum breakpoint_result breakpoint_create_instruction(struct breakpoint_handle *out_handle,
124 void *virt_addr)
125 {
126 enum breakpoint_result res =
127 allocate_breakpoint(out_handle, BREAKPOINT_TYPE_INSTRUCTION);
128
129 if (res != BREAKPOINT_RES_OK)
130 return res;
131
132 int bp = out_handle->bp;
133 if (!debug_write_addr_reg(bp, (uintptr_t)virt_addr))
134 return BREAKPOINT_RES_INVALID_HANDLE;
135
136 uintptr_t control = debug_read_control();
137 control &= ~DEBUG_CTRL_LT_MASK(bp);
138 control |= DEBUG_CTRL_LT(bp, DEBUG_CTRL_LEN_1, BREAKPOINT_TYPE_INSTRUCTION);
139 debug_write_control(control);
140 return BREAKPOINT_RES_OK;
141 }
142
breakpoint_create_data(struct breakpoint_handle * out_handle,void * virt_addr,size_t len,bool write_only)143 enum breakpoint_result breakpoint_create_data(struct breakpoint_handle *out_handle,
144 void *virt_addr, size_t len, bool write_only)
145 {
146 uintptr_t len_value = 0;
147
148 switch (len) {
149 case 1:
150 len_value = DEBUG_CTRL_LEN_1;
151 break;
152
153 case 2:
154 len_value = DEBUG_CTRL_LEN_2;
155 break;
156
157 case 4:
158 len_value = DEBUG_CTRL_LEN_4;
159 break;
160
161 case 8:
162 /* Only supported on 64-bit CPUs */
163 if (!ENV_X86_64)
164 return BREAKPOINT_RES_INVALID_LENGTH;
165 len_value = DEBUG_CTRL_LEN_8;
166 break;
167
168 default:
169 return BREAKPOINT_RES_INVALID_LENGTH;
170 }
171
172 enum breakpoint_type type =
173 write_only ? BREAKPOINT_TYPE_DATA_WRITE : BREAKPOINT_TYPE_DATA_RW;
174 enum breakpoint_result res = allocate_breakpoint(out_handle, type);
175 if (res != BREAKPOINT_RES_OK)
176 return res;
177
178 int bp = out_handle->bp;
179 if (!debug_write_addr_reg(bp, (uintptr_t)virt_addr))
180 return BREAKPOINT_RES_INVALID_HANDLE;
181
182 uintptr_t control = debug_read_control();
183 control &= ~DEBUG_CTRL_LT_MASK(bp);
184 control |= DEBUG_CTRL_LT(bp, len_value, type);
185 debug_write_control(control);
186 return BREAKPOINT_RES_OK;
187 }
188
breakpoint_remove(struct breakpoint_handle handle)189 enum breakpoint_result breakpoint_remove(struct breakpoint_handle handle)
190 {
191 enum breakpoint_result res = validate_handle(handle);
192
193 if (res != BREAKPOINT_RES_OK)
194 return res;
195 breakpoint_enable(handle, false);
196
197 int bp = handle.bp;
198 breakpoints[bp].allocated = false;
199 return BREAKPOINT_RES_OK;
200 }
201
breakpoint_enable(struct breakpoint_handle handle,bool enabled)202 enum breakpoint_result breakpoint_enable(struct breakpoint_handle handle, bool enabled)
203 {
204 enum breakpoint_result res = validate_handle(handle);
205
206 if (res != BREAKPOINT_RES_OK)
207 return res;
208
209 uintptr_t control = debug_read_control();
210 int bp = handle.bp;
211 control &= ~DEBUG_CTRL_ENABLE_MASK(bp);
212 if (enabled)
213 control |= DEBUG_CTRL_ENABLE(bp, DEBUG_CTRL_ENABLE_GLOBAL);
214 debug_write_control(control);
215 return BREAKPOINT_RES_OK;
216 }
217
breakpoint_get_type(struct breakpoint_handle handle,enum breakpoint_type * type)218 enum breakpoint_result breakpoint_get_type(struct breakpoint_handle handle,
219 enum breakpoint_type *type)
220 {
221 enum breakpoint_result res = validate_handle(handle);
222
223 if (res != BREAKPOINT_RES_OK)
224 return res;
225
226 *type = breakpoints[handle.bp].type;
227 return BREAKPOINT_RES_OK;
228 }
229
breakpoint_set_handler(struct breakpoint_handle handle,breakpoint_handler handler)230 enum breakpoint_result breakpoint_set_handler(struct breakpoint_handle handle,
231 breakpoint_handler handler)
232 {
233 enum breakpoint_result res = validate_handle(handle);
234
235 if (res != BREAKPOINT_RES_OK)
236 return res;
237
238 breakpoints[handle.bp].handler = handler;
239 return BREAKPOINT_RES_OK;
240 }
241
is_breakpoint_hit(struct breakpoint_handle handle,bool * out_hit)242 static enum breakpoint_result is_breakpoint_hit(struct breakpoint_handle handle, bool *out_hit)
243 {
244 enum breakpoint_result res = validate_handle(handle);
245
246 if (res != BREAKPOINT_RES_OK)
247 return res;
248
249 uintptr_t status = debug_read_status();
250 *out_hit = DEBUG_STATUS_GET_BP_HIT(handle.bp, status);
251
252 return BREAKPOINT_RES_OK;
253 }
254
breakpoint_dispatch_handler(struct eregs * info)255 int breakpoint_dispatch_handler(struct eregs *info)
256 {
257 bool instr_bp_hit = 0;
258
259 for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
260 struct breakpoint_handle handle = { i };
261 bool hit = false;
262 enum breakpoint_type type;
263
264 if (is_breakpoint_hit(handle, &hit) != BREAKPOINT_RES_OK || !hit)
265 continue;
266
267 if (breakpoint_get_type(handle, &type) != BREAKPOINT_RES_OK)
268 continue;
269
270 instr_bp_hit |= type == BREAKPOINT_TYPE_INSTRUCTION;
271
272 /* Call the breakpoint handler. */
273 if (breakpoints[handle.bp].handler) {
274 int ret = breakpoints[handle.bp].handler(handle, info);
275 /* A non-zero return value indicates a fatal error. */
276 if (ret)
277 return ret;
278 }
279 }
280
281 /* Clear hit breakpoints. */
282 uintptr_t status = debug_read_status();
283 for (int i = 0; i < DEBUG_REGISTER_COUNT; i++) {
284 status &= ~DEBUG_STATUS_BP_HIT_MASK(i);
285 }
286 debug_write_status(status);
287
288 if (instr_bp_hit) {
289 /* Set the resume flag so the same breakpoint won't be hit immediately. */
290 #if ENV_X86_64
291 info->rflags |= FLAGS_RESUME;
292 #else
293 info->eflags |= FLAGS_RESUME;
294 #endif
295 }
296
297 return 0;
298 }
299