aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86/kernel/alternative.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/x86/kernel/alternative.c')
-rw-r--r--arch/x86/kernel/alternative.c158
1 files changed, 136 insertions, 22 deletions
diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c
index 703130f469ec..af397cc98d05 100644
--- a/arch/x86/kernel/alternative.c
+++ b/arch/x86/kernel/alternative.c
@@ -52,10 +52,25 @@ static int __init setup_noreplace_paravirt(char *str)
52__setup("noreplace-paravirt", setup_noreplace_paravirt); 52__setup("noreplace-paravirt", setup_noreplace_paravirt);
53#endif 53#endif
54 54
55#define DPRINTK(fmt, ...) \ 55#define DPRINTK(fmt, args...) \
56do { \ 56do { \
57 if (debug_alternative) \ 57 if (debug_alternative) \
58 printk(KERN_DEBUG fmt, ##__VA_ARGS__); \ 58 printk(KERN_DEBUG "%s: " fmt "\n", __func__, ##args); \
59} while (0)
60
61#define DUMP_BYTES(buf, len, fmt, args...) \
62do { \
63 if (unlikely(debug_alternative)) { \
64 int j; \
65 \
66 if (!(len)) \
67 break; \
68 \
69 printk(KERN_DEBUG fmt, ##args); \
70 for (j = 0; j < (len) - 1; j++) \
71 printk(KERN_CONT "%02hhx ", buf[j]); \
72 printk(KERN_CONT "%02hhx\n", buf[j]); \
73 } \
59} while (0) 74} while (0)
60 75
61/* 76/*
@@ -243,12 +258,86 @@ extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
243extern s32 __smp_locks[], __smp_locks_end[]; 258extern s32 __smp_locks[], __smp_locks_end[];
244void *text_poke_early(void *addr, const void *opcode, size_t len); 259void *text_poke_early(void *addr, const void *opcode, size_t len);
245 260
246/* Replace instructions with better alternatives for this CPU type. 261/*
247 This runs before SMP is initialized to avoid SMP problems with 262 * Are we looking at a near JMP with a 1 or 4-byte displacement.
248 self modifying code. This implies that asymmetric systems where 263 */
249 APs have less capabilities than the boot processor are not handled. 264static inline bool is_jmp(const u8 opcode)
250 Tough. Make sure you disable such features by hand. */ 265{
266 return opcode == 0xeb || opcode == 0xe9;
267}
268
269static void __init_or_module
270recompute_jump(struct alt_instr *a, u8 *orig_insn, u8 *repl_insn, u8 *insnbuf)
271{
272 u8 *next_rip, *tgt_rip;
273 s32 n_dspl, o_dspl;
274 int repl_len;
275
276 if (a->replacementlen != 5)
277 return;
278
279 o_dspl = *(s32 *)(insnbuf + 1);
280
281 /* next_rip of the replacement JMP */
282 next_rip = repl_insn + a->replacementlen;
283 /* target rip of the replacement JMP */
284 tgt_rip = next_rip + o_dspl;
285 n_dspl = tgt_rip - orig_insn;
286
287 DPRINTK("target RIP: %p, new_displ: 0x%x", tgt_rip, n_dspl);
288
289 if (tgt_rip - orig_insn >= 0) {
290 if (n_dspl - 2 <= 127)
291 goto two_byte_jmp;
292 else
293 goto five_byte_jmp;
294 /* negative offset */
295 } else {
296 if (((n_dspl - 2) & 0xff) == (n_dspl - 2))
297 goto two_byte_jmp;
298 else
299 goto five_byte_jmp;
300 }
301
302two_byte_jmp:
303 n_dspl -= 2;
304
305 insnbuf[0] = 0xeb;
306 insnbuf[1] = (s8)n_dspl;
307 add_nops(insnbuf + 2, 3);
308
309 repl_len = 2;
310 goto done;
311
312five_byte_jmp:
313 n_dspl -= 5;
251 314
315 insnbuf[0] = 0xe9;
316 *(s32 *)&insnbuf[1] = n_dspl;
317
318 repl_len = 5;
319
320done:
321
322 DPRINTK("final displ: 0x%08x, JMP 0x%lx",
323 n_dspl, (unsigned long)orig_insn + n_dspl + repl_len);
324}
325
326static void __init_or_module optimize_nops(struct alt_instr *a, u8 *instr)
327{
328 add_nops(instr + (a->instrlen - a->padlen), a->padlen);
329
330 DUMP_BYTES(instr, a->instrlen, "%p: [%d:%d) optimized NOPs: ",
331 instr, a->instrlen - a->padlen, a->padlen);
332}
333
334/*
335 * Replace instructions with better alternatives for this CPU type. This runs
336 * before SMP is initialized to avoid SMP problems with self modifying code.
337 * This implies that asymmetric systems where APs have less capabilities than
338 * the boot processor are not handled. Tough. Make sure you disable such
339 * features by hand.
340 */
252void __init_or_module apply_alternatives(struct alt_instr *start, 341void __init_or_module apply_alternatives(struct alt_instr *start,
253 struct alt_instr *end) 342 struct alt_instr *end)
254{ 343{
@@ -256,10 +345,10 @@ void __init_or_module apply_alternatives(struct alt_instr *start,
256 u8 *instr, *replacement; 345 u8 *instr, *replacement;
257 u8 insnbuf[MAX_PATCH_LEN]; 346 u8 insnbuf[MAX_PATCH_LEN];
258 347
259 DPRINTK("%s: alt table %p -> %p\n", __func__, start, end); 348 DPRINTK("alt table %p -> %p", start, end);
260 /* 349 /*
261 * The scan order should be from start to end. A later scanned 350 * The scan order should be from start to end. A later scanned
262 * alternative code can overwrite a previous scanned alternative code. 351 * alternative code can overwrite previously scanned alternative code.
263 * Some kernel functions (e.g. memcpy, memset, etc) use this order to 352 * Some kernel functions (e.g. memcpy, memset, etc) use this order to
264 * patch code. 353 * patch code.
265 * 354 *
@@ -267,29 +356,54 @@ void __init_or_module apply_alternatives(struct alt_instr *start,
267 * order. 356 * order.
268 */ 357 */
269 for (a = start; a < end; a++) { 358 for (a = start; a < end; a++) {
359 int insnbuf_sz = 0;
360
270 instr = (u8 *)&a->instr_offset + a->instr_offset; 361 instr = (u8 *)&a->instr_offset + a->instr_offset;
271 replacement = (u8 *)&a->repl_offset + a->repl_offset; 362 replacement = (u8 *)&a->repl_offset + a->repl_offset;
272 BUG_ON(a->replacementlen > a->instrlen);
273 BUG_ON(a->instrlen > sizeof(insnbuf)); 363 BUG_ON(a->instrlen > sizeof(insnbuf));
274 BUG_ON(a->cpuid >= (NCAPINTS + NBUGINTS) * 32); 364 BUG_ON(a->cpuid >= (NCAPINTS + NBUGINTS) * 32);
275 if (!boot_cpu_has(a->cpuid)) 365 if (!boot_cpu_has(a->cpuid)) {
366 if (a->padlen > 1)
367 optimize_nops(a, instr);
368
276 continue; 369 continue;
370 }
371
372 DPRINTK("feat: %d*32+%d, old: (%p, len: %d), repl: (%p, len: %d)",
373 a->cpuid >> 5,
374 a->cpuid & 0x1f,
375 instr, a->instrlen,
376 replacement, a->replacementlen);
377
378 DUMP_BYTES(instr, a->instrlen, "%p: old_insn: ", instr);
379 DUMP_BYTES(replacement, a->replacementlen, "%p: rpl_insn: ", replacement);
277 380
278 memcpy(insnbuf, replacement, a->replacementlen); 381 memcpy(insnbuf, replacement, a->replacementlen);
382 insnbuf_sz = a->replacementlen;
279 383
280 /* 0xe8 is a relative jump; fix the offset. */ 384 /* 0xe8 is a relative jump; fix the offset. */
281 if (*insnbuf == 0xe8 && a->replacementlen == 5) 385 if (*insnbuf == 0xe8 && a->replacementlen == 5) {
282 *(s32 *)(insnbuf + 1) += replacement - instr; 386 *(s32 *)(insnbuf + 1) += replacement - instr;
387 DPRINTK("Fix CALL offset: 0x%x, CALL 0x%lx",
388 *(s32 *)(insnbuf + 1),
389 (unsigned long)instr + *(s32 *)(insnbuf + 1) + 5);
390 }
391
392 if (a->replacementlen && is_jmp(replacement[0]))
393 recompute_jump(a, instr, replacement, insnbuf);
283 394
284 add_nops(insnbuf + a->replacementlen, 395 if (a->instrlen > a->replacementlen) {
285 a->instrlen - a->replacementlen); 396 add_nops(insnbuf + a->replacementlen,
397 a->instrlen - a->replacementlen);
398 insnbuf_sz += a->instrlen - a->replacementlen;
399 }
400 DUMP_BYTES(insnbuf, insnbuf_sz, "%p: final_insn: ", instr);
286 401
287 text_poke_early(instr, insnbuf, a->instrlen); 402 text_poke_early(instr, insnbuf, insnbuf_sz);
288 } 403 }
289} 404}
290 405
291#ifdef CONFIG_SMP 406#ifdef CONFIG_SMP
292
293static void alternatives_smp_lock(const s32 *start, const s32 *end, 407static void alternatives_smp_lock(const s32 *start, const s32 *end,
294 u8 *text, u8 *text_end) 408 u8 *text, u8 *text_end)
295{ 409{
@@ -371,8 +485,8 @@ void __init_or_module alternatives_smp_module_add(struct module *mod,
371 smp->locks_end = locks_end; 485 smp->locks_end = locks_end;
372 smp->text = text; 486 smp->text = text;
373 smp->text_end = text_end; 487 smp->text_end = text_end;
374 DPRINTK("%s: locks %p -> %p, text %p -> %p, name %s\n", 488 DPRINTK("locks %p -> %p, text %p -> %p, name %s\n",
375 __func__, smp->locks, smp->locks_end, 489 smp->locks, smp->locks_end,
376 smp->text, smp->text_end, smp->name); 490 smp->text, smp->text_end, smp->name);
377 491
378 list_add_tail(&smp->next, &smp_alt_modules); 492 list_add_tail(&smp->next, &smp_alt_modules);
@@ -440,7 +554,7 @@ int alternatives_text_reserved(void *start, void *end)
440 554
441 return 0; 555 return 0;
442} 556}
443#endif 557#endif /* CONFIG_SMP */
444 558
445#ifdef CONFIG_PARAVIRT 559#ifdef CONFIG_PARAVIRT
446void __init_or_module apply_paravirt(struct paravirt_patch_site *start, 560void __init_or_module apply_paravirt(struct paravirt_patch_site *start,