parisc: add parisc code patching

Instead of re-mapping the whole kernel text with RWX rights
add a patch_text() which can be used to replace instructions
in the kernel .text section. Based on the ARM implementation.

Signed-off-by: Sven Schnelle <svens@stackframe.org>
Signed-off-by: Helge Deller <deller@gmx.de>
This commit is contained in:
Sven Schnelle 2019-04-04 21:14:09 +02:00 committed by Helge Deller
parent ccfbc68d41
commit 620a53d522
3 changed files with 91 additions and 1 deletions

View File

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _PARISC_KERNEL_PATCH_H
#define _PARISC_KERNEL_PATCH_H
/* stop machine and patch kernel text */
void patch_text(void *addr, unsigned int insn);
/* patch kernel text with machine already stopped (e.g. in kgdb) */
void __patch_text(void *addr, unsigned int insn);
#endif

View File

@ -9,7 +9,8 @@ obj-y := cache.o pacache.o setup.o pdt.o traps.o time.o irq.o \
pa7300lc.o syscall.o entry.o sys_parisc.o firmware.o \
ptrace.o hardware.o inventory.o drivers.o alternative.o \
signal.o hpmc.o real2.o parisc_ksyms.o unaligned.o \
process.o processor.o pdc_cons.o pdc_chassis.o unwind.o
process.o processor.o pdc_cons.o pdc_chassis.o unwind.o \
patch.o
ifdef CONFIG_FUNCTION_TRACER
# Do not profile debug and lowlevel utilities

View File

@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-2.0
/*
* functions to patch RO kernel text during runtime
*
* Copyright (c) 2019 Sven Schnelle <svens@stackframe.org>
*/
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/kprobes.h>
#include <linux/mm.h>
#include <linux/stop_machine.h>
#include <asm/cacheflush.h>
#include <asm/fixmap.h>
#include <asm/patch.h>
struct patch {
void *addr;
unsigned int insn;
};
static void __kprobes *patch_map(void *addr, int fixmap, unsigned long *flags)
{
unsigned long uintaddr = (uintptr_t) addr;
bool module = !core_kernel_text(uintaddr);
struct page *page;
if (module && IS_ENABLED(CONFIG_STRICT_MODULE_RWX))
page = vmalloc_to_page(addr);
else if (!module && IS_ENABLED(CONFIG_STRICT_KERNEL_RWX))
page = virt_to_page(addr);
else
return addr;
set_fixmap(fixmap, page_to_phys(page));
return (void *) (__fix_to_virt(fixmap) + (uintaddr & ~PAGE_MASK));
}
static void __kprobes patch_unmap(int fixmap, unsigned long *flags)
{
clear_fixmap(fixmap);
}
void __kprobes __patch_text(void *addr, unsigned int insn)
{
unsigned long flags;
void *waddr = addr;
int size;
waddr = patch_map(addr, FIX_TEXT_POKE0, &flags);
*(u32 *)waddr = insn;
size = sizeof(u32);
flush_kernel_vmap_range(waddr, size);
patch_unmap(FIX_TEXT_POKE0, &flags);
flush_icache_range((uintptr_t)(addr),
(uintptr_t)(addr) + size);
}
static int __kprobes patch_text_stop_machine(void *data)
{
struct patch *patch = data;
__patch_text(patch->addr, patch->insn);
return 0;
}
void __kprobes patch_text(void *addr, unsigned int insn)
{
struct patch patch = {
.addr = addr,
.insn = insn,
};
stop_machine_cpuslocked(patch_text_stop_machine, &patch, NULL);
}