diff options
Diffstat (limited to 'arch/x86/kernel/alternative.c')
-rw-r--r-- | arch/x86/kernel/alternative.c | 83 |
1 files changed, 82 insertions, 1 deletions
diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index de7353c0ce9c..1a160d5d44d0 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c | |||
@@ -7,6 +7,8 @@ | |||
7 | #include <linux/mm.h> | 7 | #include <linux/mm.h> |
8 | #include <linux/vmalloc.h> | 8 | #include <linux/vmalloc.h> |
9 | #include <linux/memory.h> | 9 | #include <linux/memory.h> |
10 | #include <linux/stop_machine.h> | ||
11 | #include <linux/slab.h> | ||
10 | #include <asm/alternative.h> | 12 | #include <asm/alternative.h> |
11 | #include <asm/sections.h> | 13 | #include <asm/sections.h> |
12 | #include <asm/pgtable.h> | 14 | #include <asm/pgtable.h> |
@@ -205,7 +207,7 @@ void __init_or_module apply_alternatives(struct alt_instr *start, | |||
205 | struct alt_instr *end) | 207 | struct alt_instr *end) |
206 | { | 208 | { |
207 | struct alt_instr *a; | 209 | struct alt_instr *a; |
208 | char insnbuf[MAX_PATCH_LEN]; | 210 | u8 insnbuf[MAX_PATCH_LEN]; |
209 | 211 | ||
210 | DPRINTK("%s: alt table %p -> %p\n", __func__, start, end); | 212 | DPRINTK("%s: alt table %p -> %p\n", __func__, start, end); |
211 | for (a = start; a < end; a++) { | 213 | for (a = start; a < end; a++) { |
@@ -223,6 +225,8 @@ void __init_or_module apply_alternatives(struct alt_instr *start, | |||
223 | } | 225 | } |
224 | #endif | 226 | #endif |
225 | memcpy(insnbuf, a->replacement, a->replacementlen); | 227 | memcpy(insnbuf, a->replacement, a->replacementlen); |
228 | if (*insnbuf == 0xe8 && a->replacementlen == 5) | ||
229 | *(s32 *)(insnbuf + 1) += a->replacement - a->instr; | ||
226 | add_nops(insnbuf + a->replacementlen, | 230 | add_nops(insnbuf + a->replacementlen, |
227 | a->instrlen - a->replacementlen); | 231 | a->instrlen - a->replacementlen); |
228 | text_poke_early(instr, insnbuf, a->instrlen); | 232 | text_poke_early(instr, insnbuf, a->instrlen); |
@@ -390,6 +394,24 @@ void alternatives_smp_switch(int smp) | |||
390 | mutex_unlock(&smp_alt); | 394 | mutex_unlock(&smp_alt); |
391 | } | 395 | } |
392 | 396 | ||
397 | /* Return 1 if the address range is reserved for smp-alternatives */ | ||
398 | int alternatives_text_reserved(void *start, void *end) | ||
399 | { | ||
400 | struct smp_alt_module *mod; | ||
401 | u8 **ptr; | ||
402 | u8 *text_start = start; | ||
403 | u8 *text_end = end; | ||
404 | |||
405 | list_for_each_entry(mod, &smp_alt_modules, next) { | ||
406 | if (mod->text > text_end || mod->text_end < text_start) | ||
407 | continue; | ||
408 | for (ptr = mod->locks; ptr < mod->locks_end; ptr++) | ||
409 | if (text_start <= *ptr && text_end >= *ptr) | ||
410 | return 1; | ||
411 | } | ||
412 | |||
413 | return 0; | ||
414 | } | ||
393 | #endif | 415 | #endif |
394 | 416 | ||
395 | #ifdef CONFIG_PARAVIRT | 417 | #ifdef CONFIG_PARAVIRT |
@@ -552,3 +574,62 @@ void *__kprobes text_poke(void *addr, const void *opcode, size_t len) | |||
552 | local_irq_restore(flags); | 574 | local_irq_restore(flags); |
553 | return addr; | 575 | return addr; |
554 | } | 576 | } |
577 | |||
578 | /* | ||
579 | * Cross-modifying kernel text with stop_machine(). | ||
580 | * This code originally comes from immediate value. | ||
581 | */ | ||
582 | static atomic_t stop_machine_first; | ||
583 | static int wrote_text; | ||
584 | |||
585 | struct text_poke_params { | ||
586 | void *addr; | ||
587 | const void *opcode; | ||
588 | size_t len; | ||
589 | }; | ||
590 | |||
591 | static int __kprobes stop_machine_text_poke(void *data) | ||
592 | { | ||
593 | struct text_poke_params *tpp = data; | ||
594 | |||
595 | if (atomic_dec_and_test(&stop_machine_first)) { | ||
596 | text_poke(tpp->addr, tpp->opcode, tpp->len); | ||
597 | smp_wmb(); /* Make sure other cpus see that this has run */ | ||
598 | wrote_text = 1; | ||
599 | } else { | ||
600 | while (!wrote_text) | ||
601 | cpu_relax(); | ||
602 | smp_mb(); /* Load wrote_text before following execution */ | ||
603 | } | ||
604 | |||
605 | flush_icache_range((unsigned long)tpp->addr, | ||
606 | (unsigned long)tpp->addr + tpp->len); | ||
607 | return 0; | ||
608 | } | ||
609 | |||
610 | /** | ||
611 | * text_poke_smp - Update instructions on a live kernel on SMP | ||
612 | * @addr: address to modify | ||
613 | * @opcode: source of the copy | ||
614 | * @len: length to copy | ||
615 | * | ||
616 | * Modify multi-byte instruction by using stop_machine() on SMP. This allows | ||
617 | * user to poke/set multi-byte text on SMP. Only non-NMI/MCE code modifying | ||
618 | * should be allowed, since stop_machine() does _not_ protect code against | ||
619 | * NMI and MCE. | ||
620 | * | ||
621 | * Note: Must be called under get_online_cpus() and text_mutex. | ||
622 | */ | ||
623 | void *__kprobes text_poke_smp(void *addr, const void *opcode, size_t len) | ||
624 | { | ||
625 | struct text_poke_params tpp; | ||
626 | |||
627 | tpp.addr = addr; | ||
628 | tpp.opcode = opcode; | ||
629 | tpp.len = len; | ||
630 | atomic_set(&stop_machine_first, 1); | ||
631 | wrote_text = 0; | ||
632 | stop_machine(stop_machine_text_poke, (void *)&tpp, NULL); | ||
633 | return addr; | ||
634 | } | ||
635 | |||