diff options
author | Mathieu Desnoyers <mathieu.desnoyers@polymtl.ca> | 2008-03-06 08:48:49 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2008-04-17 11:40:58 -0400 |
commit | e587cadd8f47e202a30712e2906a65a0606d5865 (patch) | |
tree | 9df9016b1cba513cc48472d335899ae5e216f8f1 /arch/x86/kernel/alternative.c | |
parent | 77bf90ed66116a1fc0e2f0554ecac75a54290cc0 (diff) |
x86: enhance DEBUG_RODATA support - alternatives
Fix a memcpy that should be a text_poke (in apply_alternatives).
Use kernel_wp_save/kernel_wp_restore in text_poke to support DEBUG_RODATA
correctly and so the CPU HOTPLUG special case can be removed.
Add text_poke_early, for alternatives and paravirt boot-time and module load
time patching.
Changelog:
- Fix text_set and text_poke alignment check (mixed up bitwise and and or)
- Remove text_set
- Export add_nops, so it can be used by others.
- Document text_poke_early.
- Remove clflush, since it breaks some VIA architectures and is not strictly
necessary.
- Add kerneldoc to text_poke and text_poke_early.
- Create a second vmap instead of using the WP bit to support Xen and VMI.
- Move local_irq disable within text_poke and text_poke_early to be able to
be sleepable in these functions.
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@polymtl.ca>
CC: Andi Kleen <andi@firstfloor.org>
CC: pageexec@freemail.hu
CC: H. Peter Anvin <hpa@zytor.com>
CC: Jeremy Fitzhardinge <jeremy@goop.org>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'arch/x86/kernel/alternative.c')
-rw-r--r-- | arch/x86/kernel/alternative.c | 88 |
1 files changed, 65 insertions, 23 deletions
diff --git a/arch/x86/kernel/alternative.c b/arch/x86/kernel/alternative.c index e2d30b8e08a2..0c92ad4d257a 100644 --- a/arch/x86/kernel/alternative.c +++ b/arch/x86/kernel/alternative.c | |||
@@ -11,6 +11,8 @@ | |||
11 | #include <asm/mce.h> | 11 | #include <asm/mce.h> |
12 | #include <asm/nmi.h> | 12 | #include <asm/nmi.h> |
13 | #include <asm/vsyscall.h> | 13 | #include <asm/vsyscall.h> |
14 | #include <asm/cacheflush.h> | ||
15 | #include <asm/io.h> | ||
14 | 16 | ||
15 | #define MAX_PATCH_LEN (255-1) | 17 | #define MAX_PATCH_LEN (255-1) |
16 | 18 | ||
@@ -177,7 +179,7 @@ static const unsigned char*const * find_nop_table(void) | |||
177 | #endif /* CONFIG_X86_64 */ | 179 | #endif /* CONFIG_X86_64 */ |
178 | 180 | ||
179 | /* Use this to add nops to a buffer, then text_poke the whole buffer. */ | 181 | /* Use this to add nops to a buffer, then text_poke the whole buffer. */ |
180 | static void add_nops(void *insns, unsigned int len) | 182 | void add_nops(void *insns, unsigned int len) |
181 | { | 183 | { |
182 | const unsigned char *const *noptable = find_nop_table(); | 184 | const unsigned char *const *noptable = find_nop_table(); |
183 | 185 | ||
@@ -190,6 +192,7 @@ static void add_nops(void *insns, unsigned int len) | |||
190 | len -= noplen; | 192 | len -= noplen; |
191 | } | 193 | } |
192 | } | 194 | } |
195 | EXPORT_SYMBOL_GPL(add_nops); | ||
193 | 196 | ||
194 | extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; | 197 | extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; |
195 | extern u8 *__smp_locks[], *__smp_locks_end[]; | 198 | extern u8 *__smp_locks[], *__smp_locks_end[]; |
@@ -223,7 +226,7 @@ void apply_alternatives(struct alt_instr *start, struct alt_instr *end) | |||
223 | memcpy(insnbuf, a->replacement, a->replacementlen); | 226 | memcpy(insnbuf, a->replacement, a->replacementlen); |
224 | add_nops(insnbuf + a->replacementlen, | 227 | add_nops(insnbuf + a->replacementlen, |
225 | a->instrlen - a->replacementlen); | 228 | a->instrlen - a->replacementlen); |
226 | text_poke(instr, insnbuf, a->instrlen); | 229 | text_poke_early(instr, insnbuf, a->instrlen); |
227 | } | 230 | } |
228 | } | 231 | } |
229 | 232 | ||
@@ -284,7 +287,6 @@ void alternatives_smp_module_add(struct module *mod, char *name, | |||
284 | void *text, void *text_end) | 287 | void *text, void *text_end) |
285 | { | 288 | { |
286 | struct smp_alt_module *smp; | 289 | struct smp_alt_module *smp; |
287 | unsigned long flags; | ||
288 | 290 | ||
289 | if (noreplace_smp) | 291 | if (noreplace_smp) |
290 | return; | 292 | return; |
@@ -310,39 +312,37 @@ void alternatives_smp_module_add(struct module *mod, char *name, | |||
310 | __func__, smp->locks, smp->locks_end, | 312 | __func__, smp->locks, smp->locks_end, |
311 | smp->text, smp->text_end, smp->name); | 313 | smp->text, smp->text_end, smp->name); |
312 | 314 | ||
313 | spin_lock_irqsave(&smp_alt, flags); | 315 | spin_lock(&smp_alt); |
314 | list_add_tail(&smp->next, &smp_alt_modules); | 316 | list_add_tail(&smp->next, &smp_alt_modules); |
315 | if (boot_cpu_has(X86_FEATURE_UP)) | 317 | if (boot_cpu_has(X86_FEATURE_UP)) |
316 | alternatives_smp_unlock(smp->locks, smp->locks_end, | 318 | alternatives_smp_unlock(smp->locks, smp->locks_end, |
317 | smp->text, smp->text_end); | 319 | smp->text, smp->text_end); |
318 | spin_unlock_irqrestore(&smp_alt, flags); | 320 | spin_unlock(&smp_alt); |
319 | } | 321 | } |
320 | 322 | ||
321 | void alternatives_smp_module_del(struct module *mod) | 323 | void alternatives_smp_module_del(struct module *mod) |
322 | { | 324 | { |
323 | struct smp_alt_module *item; | 325 | struct smp_alt_module *item; |
324 | unsigned long flags; | ||
325 | 326 | ||
326 | if (smp_alt_once || noreplace_smp) | 327 | if (smp_alt_once || noreplace_smp) |
327 | return; | 328 | return; |
328 | 329 | ||
329 | spin_lock_irqsave(&smp_alt, flags); | 330 | spin_lock(&smp_alt); |
330 | list_for_each_entry(item, &smp_alt_modules, next) { | 331 | list_for_each_entry(item, &smp_alt_modules, next) { |
331 | if (mod != item->mod) | 332 | if (mod != item->mod) |
332 | continue; | 333 | continue; |
333 | list_del(&item->next); | 334 | list_del(&item->next); |
334 | spin_unlock_irqrestore(&smp_alt, flags); | 335 | spin_unlock(&smp_alt); |
335 | DPRINTK("%s: %s\n", __func__, item->name); | 336 | DPRINTK("%s: %s\n", __func__, item->name); |
336 | kfree(item); | 337 | kfree(item); |
337 | return; | 338 | return; |
338 | } | 339 | } |
339 | spin_unlock_irqrestore(&smp_alt, flags); | 340 | spin_unlock(&smp_alt); |
340 | } | 341 | } |
341 | 342 | ||
342 | void alternatives_smp_switch(int smp) | 343 | void alternatives_smp_switch(int smp) |
343 | { | 344 | { |
344 | struct smp_alt_module *mod; | 345 | struct smp_alt_module *mod; |
345 | unsigned long flags; | ||
346 | 346 | ||
347 | #ifdef CONFIG_LOCKDEP | 347 | #ifdef CONFIG_LOCKDEP |
348 | /* | 348 | /* |
@@ -359,7 +359,7 @@ void alternatives_smp_switch(int smp) | |||
359 | return; | 359 | return; |
360 | BUG_ON(!smp && (num_online_cpus() > 1)); | 360 | BUG_ON(!smp && (num_online_cpus() > 1)); |
361 | 361 | ||
362 | spin_lock_irqsave(&smp_alt, flags); | 362 | spin_lock(&smp_alt); |
363 | 363 | ||
364 | /* | 364 | /* |
365 | * Avoid unnecessary switches because it forces JIT based VMs to | 365 | * Avoid unnecessary switches because it forces JIT based VMs to |
@@ -383,7 +383,7 @@ void alternatives_smp_switch(int smp) | |||
383 | mod->text, mod->text_end); | 383 | mod->text, mod->text_end); |
384 | } | 384 | } |
385 | smp_mode = smp; | 385 | smp_mode = smp; |
386 | spin_unlock_irqrestore(&smp_alt, flags); | 386 | spin_unlock(&smp_alt); |
387 | } | 387 | } |
388 | 388 | ||
389 | #endif | 389 | #endif |
@@ -411,7 +411,7 @@ void apply_paravirt(struct paravirt_patch_site *start, | |||
411 | 411 | ||
412 | /* Pad the rest with nops */ | 412 | /* Pad the rest with nops */ |
413 | add_nops(insnbuf + used, p->len - used); | 413 | add_nops(insnbuf + used, p->len - used); |
414 | text_poke(p->instr, insnbuf, p->len); | 414 | text_poke_early(p->instr, insnbuf, p->len); |
415 | } | 415 | } |
416 | } | 416 | } |
417 | extern struct paravirt_patch_site __start_parainstructions[], | 417 | extern struct paravirt_patch_site __start_parainstructions[], |
@@ -420,8 +420,6 @@ extern struct paravirt_patch_site __start_parainstructions[], | |||
420 | 420 | ||
421 | void __init alternative_instructions(void) | 421 | void __init alternative_instructions(void) |
422 | { | 422 | { |
423 | unsigned long flags; | ||
424 | |||
425 | /* The patching is not fully atomic, so try to avoid local interruptions | 423 | /* The patching is not fully atomic, so try to avoid local interruptions |
426 | that might execute the to be patched code. | 424 | that might execute the to be patched code. |
427 | Other CPUs are not running. */ | 425 | Other CPUs are not running. */ |
@@ -430,7 +428,6 @@ void __init alternative_instructions(void) | |||
430 | stop_mce(); | 428 | stop_mce(); |
431 | #endif | 429 | #endif |
432 | 430 | ||
433 | local_irq_save(flags); | ||
434 | apply_alternatives(__alt_instructions, __alt_instructions_end); | 431 | apply_alternatives(__alt_instructions, __alt_instructions_end); |
435 | 432 | ||
436 | /* switch to patch-once-at-boottime-only mode and free the | 433 | /* switch to patch-once-at-boottime-only mode and free the |
@@ -462,7 +459,6 @@ void __init alternative_instructions(void) | |||
462 | } | 459 | } |
463 | #endif | 460 | #endif |
464 | apply_paravirt(__parainstructions, __parainstructions_end); | 461 | apply_paravirt(__parainstructions, __parainstructions_end); |
465 | local_irq_restore(flags); | ||
466 | 462 | ||
467 | if (smp_alt_once) | 463 | if (smp_alt_once) |
468 | free_init_pages("SMP alternatives", | 464 | free_init_pages("SMP alternatives", |
@@ -475,18 +471,64 @@ void __init alternative_instructions(void) | |||
475 | #endif | 471 | #endif |
476 | } | 472 | } |
477 | 473 | ||
478 | /* | 474 | /** |
479 | * Warning: | 475 | * text_poke_early - Update instructions on a live kernel at boot time |
476 | * @addr: address to modify | ||
477 | * @opcode: source of the copy | ||
478 | * @len: length to copy | ||
479 | * | ||
480 | * When you use this code to patch more than one byte of an instruction | 480 | * When you use this code to patch more than one byte of an instruction |
481 | * you need to make sure that other CPUs cannot execute this code in parallel. | 481 | * you need to make sure that other CPUs cannot execute this code in parallel. |
482 | * Also no thread must be currently preempted in the middle of these instructions. | 482 | * Also no thread must be currently preempted in the middle of these |
483 | * And on the local CPU you need to be protected again NMI or MCE handlers | 483 | * instructions. And on the local CPU you need to be protected again NMI or MCE |
484 | * seeing an inconsistent instruction while you patch. | 484 | * handlers seeing an inconsistent instruction while you patch. |
485 | */ | 485 | */ |
486 | void __kprobes text_poke(void *addr, unsigned char *opcode, int len) | 486 | void *text_poke_early(void *addr, const void *opcode, size_t len) |
487 | { | 487 | { |
488 | unsigned long flags; | ||
489 | local_irq_save(flags); | ||
488 | memcpy(addr, opcode, len); | 490 | memcpy(addr, opcode, len); |
491 | local_irq_restore(flags); | ||
492 | sync_core(); | ||
493 | /* Could also do a CLFLUSH here to speed up CPU recovery; but | ||
494 | that causes hangs on some VIA CPUs. */ | ||
495 | return addr; | ||
496 | } | ||
497 | |||
498 | /** | ||
499 | * text_poke - Update instructions on a live kernel | ||
500 | * @addr: address to modify | ||
501 | * @opcode: source of the copy | ||
502 | * @len: length to copy | ||
503 | * | ||
504 | * Only atomic text poke/set should be allowed when not doing early patching. | ||
505 | * It means the size must be writable atomically and the address must be aligned | ||
506 | * in a way that permits an atomic write. It also makes sure we fit on a single | ||
507 | * page. | ||
508 | */ | ||
509 | void *__kprobes text_poke(void *addr, const void *opcode, size_t len) | ||
510 | { | ||
511 | unsigned long flags; | ||
512 | char *vaddr; | ||
513 | int nr_pages = 2; | ||
514 | |||
515 | BUG_ON(len > sizeof(long)); | ||
516 | BUG_ON((((long)addr + len - 1) & ~(sizeof(long) - 1)) | ||
517 | - ((long)addr & ~(sizeof(long) - 1))); | ||
518 | { | ||
519 | struct page *pages[2] = { virt_to_page(addr), | ||
520 | virt_to_page(addr + PAGE_SIZE) }; | ||
521 | if (!pages[1]) | ||
522 | nr_pages = 1; | ||
523 | vaddr = vmap(pages, nr_pages, VM_MAP, PAGE_KERNEL); | ||
524 | BUG_ON(!vaddr); | ||
525 | local_irq_save(flags); | ||
526 | memcpy(&vaddr[(unsigned long)addr & ~PAGE_MASK], opcode, len); | ||
527 | local_irq_restore(flags); | ||
528 | vunmap(vaddr); | ||
529 | } | ||
489 | sync_core(); | 530 | sync_core(); |
490 | /* Could also do a CLFLUSH here to speed up CPU recovery; but | 531 | /* Could also do a CLFLUSH here to speed up CPU recovery; but |
491 | that causes hangs on some VIA CPUs. */ | 532 | that causes hangs on some VIA CPUs. */ |
533 | return addr; | ||
492 | } | 534 | } |