diff options
Diffstat (limited to 'arch/x86/kernel/alternative.c')
-rw-r--r-- | arch/x86/kernel/alternative.c | 158 |
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...) \ |
56 | do { \ | 56 | do { \ |
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...) \ | ||
62 | do { \ | ||
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[]; | |||
243 | extern s32 __smp_locks[], __smp_locks_end[]; | 258 | extern s32 __smp_locks[], __smp_locks_end[]; |
244 | void *text_poke_early(void *addr, const void *opcode, size_t len); | 259 | void *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. | 264 | static 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 | |||
269 | static void __init_or_module | ||
270 | recompute_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 | |||
302 | two_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 | |||
312 | five_byte_jmp: | ||
313 | n_dspl -= 5; | ||
251 | 314 | ||
315 | insnbuf[0] = 0xe9; | ||
316 | *(s32 *)&insnbuf[1] = n_dspl; | ||
317 | |||
318 | repl_len = 5; | ||
319 | |||
320 | done: | ||
321 | |||
322 | DPRINTK("final displ: 0x%08x, JMP 0x%lx", | ||
323 | n_dspl, (unsigned long)orig_insn + n_dspl + repl_len); | ||
324 | } | ||
325 | |||
326 | static 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 | */ | ||
252 | void __init_or_module apply_alternatives(struct alt_instr *start, | 341 | void __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 | |||
293 | static void alternatives_smp_lock(const s32 *start, const s32 *end, | 407 | static 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 |
446 | void __init_or_module apply_paravirt(struct paravirt_patch_site *start, | 560 | void __init_or_module apply_paravirt(struct paravirt_patch_site *start, |