This commit adds generic infrastructure to do binary code patching on amd64.
The existing code patching for SMAP is converted to the new infrastruture.
More consumers and support for i386 will follow later.
This version of the diff has some simplifications in codepatch_fill_nop()
compared to a version that was:
OK @kettenis @mlarkin @jsg
--- /dev/null
+/* $OpenBSD: codepatch.c,v 1.1 2015/01/16 10:17:51 sf Exp $ */
+/*
+ * Copyright (c) 2014-2015 Stefan Fritsch <sf@sfritsch.de>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <machine/codepatch.h>
+#include <uvm/uvm_extern.h>
+
+#ifdef CODEPATCH_DEBUG
+#define DBGPRINT(fmt, args...) printf("%s: " fmt "\n", __func__, ## args)
+#else
+#define DBGPRINT(fmt, args...) do {} while (0)
+#endif
+
+struct codepatch {
+ uint32_t offset;
+ uint16_t len;
+ uint16_t tag;
+};
+
+extern struct codepatch codepatch_begin;
+extern struct codepatch codepatch_end;
+
+#define NOP_LEN_MAX 9
+
+static const unsigned char nops[][NOP_LEN_MAX] = {
+ { 0x90 },
+ { 0x66, 0x90 },
+ { 0x0F, 0x1F, 0x00 },
+ { 0x0F, 0x1F, 0x40, 0x00 },
+ { 0x0F, 0x1F, 0x44, 0x00, 0x00 },
+ { 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00 },
+ { 0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00 },
+ { 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ { 0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00},
+};
+
+void
+codepatch_fill_nop(void *caddr, uint16_t len)
+{
+ unsigned char *addr = caddr;
+ uint16_t nop_len;
+
+ while (len > 0) {
+ if (len <= NOP_LEN_MAX)
+ nop_len = len;
+ else
+ nop_len = NOP_LEN_MAX;
+ memcpy(addr, nops[nop_len-1], nop_len);
+ addr += nop_len;
+ len -= nop_len;
+ }
+}
+
+/*
+ * Create writeable aliases of memory we need
+ * to write to as kernel is mapped read-only
+ */
+void *codepatch_maprw(vaddr_t *nva, vaddr_t dest)
+{
+ paddr_t kva = trunc_page((paddr_t)dest);
+ paddr_t po = (paddr_t)dest & PAGE_MASK;
+ paddr_t pa1, pa2;
+
+ if (*nva == 0)
+ *nva = (vaddr_t)km_alloc(2 * PAGE_SIZE, &kv_any, &kp_none,
+ &kd_waitok);
+
+ pmap_extract(pmap_kernel(), kva, &pa1);
+ pmap_extract(pmap_kernel(), kva + PAGE_SIZE, &pa2);
+ pmap_kenter_pa(*nva, pa1, PROT_READ | PROT_WRITE);
+ pmap_kenter_pa(*nva + PAGE_SIZE, pa2, PROT_READ | PROT_WRITE);
+ pmap_update(pmap_kernel());
+
+ return (void *)(*nva + po);
+}
+
+void codepatch_unmaprw(vaddr_t nva)
+{
+ if (nva != 0)
+ km_free((void *)nva, 2 * PAGE_SIZE, &kv_any, &kp_none);
+}
+
+/* Patch with NOPs */
+void
+codepatch_nop(uint16_t tag)
+{
+ struct codepatch *patch;
+ unsigned char *rwaddr;
+ vaddr_t addr, rwmap = 0;
+ int i = 0;
+
+ DBGPRINT("patching tag %u", tag);
+
+ for (patch = &codepatch_begin; patch < &codepatch_end; patch++) {
+ if (patch->tag != tag)
+ continue;
+ addr = KERNBASE + patch->offset;
+ rwaddr = codepatch_maprw(&rwmap, addr);
+ codepatch_fill_nop(rwaddr, patch->len);
+ i++;
+ }
+ codepatch_unmaprw(rwmap);
+ DBGPRINT("patched %d places", i);
+}
+
+/* Patch with alternative code */
+void
+codepatch_replace(uint16_t tag, void *code, size_t len)
+{
+ struct codepatch *patch;
+ unsigned char *rwaddr;
+ vaddr_t addr, rwmap = 0;
+ int i = 0;
+
+ DBGPRINT("patching tag %u with %p", tag, code);
+
+ for (patch = &codepatch_begin; patch < &codepatch_end; patch++) {
+ if (patch->tag != tag)
+ continue;
+ addr = KERNBASE + patch->offset;
+
+ if (len > patch->len) {
+ panic("%s: can't replace len %u with %zu at %#lx",
+ __func__, patch->len, len, addr);
+ }
+ rwaddr = codepatch_maprw(&rwmap, addr);
+ memcpy(rwaddr, code, len);
+ codepatch_fill_nop(rwaddr + len, patch->len - len);
+ i++;
+ }
+ codepatch_unmaprw(rwmap);
+ DBGPRINT("patched %d places", i);
+}
+
+/* Patch with calls to func */
+void
+codepatch_call(uint16_t tag, void *func)
+{
+ struct codepatch *patch;
+ unsigned char *rwaddr;
+ int32_t offset;
+ int i = 0;
+ vaddr_t addr, rwmap = 0;
+
+ DBGPRINT("patching tag %u with call %p", tag, func);
+
+ for (patch = &codepatch_begin; patch < &codepatch_end; patch++) {
+ if (patch->tag != tag)
+ continue;
+ addr = KERNBASE + patch->offset;
+ if (patch->len < 5)
+ panic("%s: can't replace len %u with call at %#lx",
+ __func__, patch->len, addr);
+
+ offset = (vaddr_t)func - (addr + 5);
+ rwaddr = codepatch_maprw(&rwmap, addr);
+ rwaddr[0] = 0xe8; /* call near */
+ memcpy(rwaddr + 1, &offset, sizeof(offset));
+ codepatch_fill_nop(rwaddr + 5, patch->len - 5);
+ i++;
+ }
+ codepatch_unmaprw(rwmap);
+ DBGPRINT("patched %d places", i);
+}
-/* $OpenBSD: copy.S,v 1.5 2012/10/31 03:30:22 jsg Exp $ */
+/* $OpenBSD: copy.S,v 1.6 2015/01/16 10:17:51 sf Exp $ */
/* $NetBSD: copy.S,v 1.1 2003/04/26 18:39:26 fvdl Exp $ */
/*
#include <sys/syscall.h>
#include <machine/asm.h>
+#include <machine/codepatch.h>
/*
* As stac/clac SMAP instructions are 3 bytes, we want the fastest
* on all family 0x6 and 0xf processors (ie 686+)
*/
#define SMAP_NOP .byte 0x0f, 0x1f, 0x00
+#define SMAP_STAC CODEPATCH_START ;\
+ SMAP_NOP ;\
+ CODEPATCH_END(CPTAG_STAC)
+#define SMAP_CLAC CODEPATCH_START ;\
+ SMAP_NOP ;\
+ CODEPATCH_END(CPTAG_CLAC)
/*
* Copy routines from and to userland, plus a few more. See the
xorq %rax,%rax
ret
-.globl _C_LABEL(_copyout_stac), _C_LABEL(_copyout_clac)
ENTRY(copyout)
pushq $0
movq CPUVAR(CURPCB),%rdx
leaq _C_LABEL(copy_fault)(%rip),%r11
movq %r11,PCB_ONFAULT(%rdx)
-_C_LABEL(_copyout_stac):
- SMAP_NOP
-
+ SMAP_STAC
cld
movq %rax,%rcx
shrq $3,%rcx
andb $7,%cl
rep
movsb
-
-_C_LABEL(_copyout_clac):
- SMAP_NOP
+ SMAP_CLAC
popq PCB_ONFAULT(%rdx)
xorl %eax,%eax
ret
-.globl _C_LABEL(_copyin_stac), _C_LABEL(_copyin_clac)
ENTRY(copyin)
movq CPUVAR(CURPCB),%rax
pushq $0
leaq _C_LABEL(copy_fault)(%rip),%r11
movq %r11,PCB_ONFAULT(%rax)
-_C_LABEL(_copyin_stac):
- SMAP_NOP
-
+ SMAP_STAC
xchgq %rdi,%rsi
movq %rdx,%rax
rep
movsb
-_C_LABEL(_copyin_clac):
- SMAP_NOP
+ SMAP_CLAC
movq CPUVAR(CURPCB),%rdx
popq PCB_ONFAULT(%rdx)
xorl %eax,%eax
NENTRY(copy_efault)
movq $EFAULT,%rax
-.globl _C_LABEL(_copy_fault_clac)
NENTRY(copy_fault)
-_C_LABEL(_copy_fault_clac):
- SMAP_NOP
+ SMAP_CLAC
movq CPUVAR(CURPCB),%rdx
popq PCB_ONFAULT(%rdx)
ret
-.globl _C_LABEL(_copyoutstr_stac)
ENTRY(copyoutstr)
xchgq %rdi,%rsi
movq %rdx,%r8
5: movq CPUVAR(CURPCB),%rax
leaq _C_LABEL(copystr_fault)(%rip),%r11
movq %r11,PCB_ONFAULT(%rax)
-_C_LABEL(_copyoutstr_stac):
- SMAP_NOP
+ SMAP_STAC
/*
* Get min(%rdx, VM_MAXUSER_ADDRESS-%rdi).
*/
movq $ENAMETOOLONG,%rax
jmp copystr_return
-.globl _C_LABEL(_copyinstr_stac)
ENTRY(copyinstr)
xchgq %rdi,%rsi
movq %rdx,%r8
movq CPUVAR(CURPCB),%rcx
leaq _C_LABEL(copystr_fault)(%rip),%r11
movq %r11,PCB_ONFAULT(%rcx)
-_C_LABEL(_copyinstr_stac):
- SMAP_NOP
+ SMAP_STAC
/*
* Get min(%rdx, VM_MAXUSER_ADDRESS-%rsi).
ENTRY(copystr_efault)
movl $EFAULT,%eax
-.globl _C_LABEL(_copystr_fault_clac)
ENTRY(copystr_fault)
copystr_return:
-_C_LABEL(_copystr_fault_clac):
- SMAP_NOP
+ SMAP_CLAC
/* Set *lencopied and return %eax. */
movq CPUVAR(CURPCB),%rcx
movq $0,PCB_ONFAULT(%rcx)
-/* $OpenBSD: cpu.c,v 1.77 2015/01/06 12:50:47 dlg Exp $ */
+/* $OpenBSD: cpu.c,v 1.78 2015/01/16 10:17:51 sf Exp $ */
/* $NetBSD: cpu.c,v 1.1 2003/04/26 18:39:26 fvdl Exp $ */
/*-
#include <uvm/uvm_extern.h>
+#include <machine/codepatch.h>
#include <machine/cpu.h>
#include <machine/cpufunc.h>
#include <machine/cpuvar.h>
#ifndef SMALL_KERNEL
void replacesmap(void);
-extern long _copyout_stac;
-extern long _copyout_clac;
-extern long _copyin_stac;
-extern long _copyin_clac;
-extern long _copy_fault_clac;
-extern long _copyoutstr_stac;
-extern long _copyinstr_stac;
-extern long _copystr_fault_clac;
extern long _stac;
extern long _clac;
-static const struct {
- void *daddr;
- void *saddr;
-} ireplace[] = {
- { &_copyout_stac, &_stac },
- { &_copyout_clac, &_clac },
- { &_copyin_stac, &_stac },
- { &_copyin_clac, &_clac },
- { &_copy_fault_clac, &_clac },
- { &_copyoutstr_stac, &_stac },
- { &_copyinstr_stac, &_stac },
- { &_copystr_fault_clac, &_clac },
-};
-
void
replacesmap(void)
{
static int replacedone = 0;
- int i, s;
- vaddr_t nva;
+ int s;
if (replacedone)
return;
replacedone = 1;
s = splhigh();
- /*
- * Create writeable aliases of memory we need
- * to write to as kernel is mapped read-only
- */
- nva = (vaddr_t)km_alloc(2 * PAGE_SIZE, &kv_any, &kp_none, &kd_waitok);
-
- for (i = 0; i < nitems(ireplace); i++) {
- paddr_t kva = trunc_page((paddr_t)ireplace[i].daddr);
- paddr_t po = (paddr_t)ireplace[i].daddr & PAGE_MASK;
- paddr_t pa1, pa2;
-
- pmap_extract(pmap_kernel(), kva, &pa1);
- pmap_extract(pmap_kernel(), kva + PAGE_SIZE, &pa2);
- pmap_kenter_pa(nva, pa1, PROT_READ | PROT_WRITE);
- pmap_kenter_pa(nva + PAGE_SIZE, pa2, PROT_READ | PROT_WRITE);
- pmap_update(pmap_kernel());
-
- /* replace 3 byte nops with stac/clac instructions */
- memcpy((void *)(nva + po), ireplace[i].saddr, 3);
- }
- km_free((void *)nva, 2 * PAGE_SIZE, &kv_any, &kp_none);
+ codepatch_replace(CPTAG_STAC, &_stac, 3);
+ codepatch_replace(CPTAG_CLAC, &_clac, 3);
splx(s);
}
-/* $OpenBSD: locore.S,v 1.61 2014/12/21 16:27:07 mlarkin Exp $ */
+/* $OpenBSD: locore.S,v 1.62 2015/01/16 10:17:51 sf Exp $ */
/* $NetBSD: locore.S,v 1.13 2004/03/25 18:33:17 drochner Exp $ */
/*
sfence
ret
+ .section .codepatch,"a"
+ .align 8
+ .globl _C_LABEL(codepatch_begin)
+_C_LABEL(codepatch_begin):
+ .previous
+
+ .section .codepatchend,"a"
+ .globl _C_LABEL(codepatch_end)
+_C_LABEL(codepatch_end):
+ .previous
-# $OpenBSD: files.amd64,v 1.76 2014/12/10 05:42:25 jsg Exp $
+# $OpenBSD: files.amd64,v 1.77 2015/01/16 10:17:51 sf Exp $
maxpartitions 16
maxusers 2 16 128
attach mainbus at root
file arch/amd64/amd64/mainbus.c mainbus
+file arch/amd64/amd64/codepatch.c
+
device bios {}
attach bios at mainbus
file arch/amd64/amd64/bios.c bios needs-flag
--- /dev/null
+/* $OpenBSD: codepatch.h,v 1.1 2015/01/16 10:17:51 sf Exp $ */
+/*
+ * Copyright (c) 2014-2015 Stefan Fritsch <sf@sfritsch.de>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _MACHINE_CODEPATCH_H_
+#define _MACHINE_CODEPATCH_H_
+
+#include <machine/param.h>
+
+#ifndef _LOCORE
+
+void *codepatch_maprw(vaddr_t *nva, vaddr_t dest);
+void codepatch_unmaprw(vaddr_t nva);
+void codepatch_fill_nop(void *caddr, uint16_t len);
+void codepatch_nop(uint16_t tag);
+void codepatch_replace(uint16_t tag, void *code, size_t len);
+void codepatch_call(uint16_t tag, void *func);
+
+#endif /* !_LOCORE */
+
+/*
+ * Mark the start of some code snippet to be patched.
+ */
+#define CODEPATCH_START 998:
+/*
+ * Mark the end of some code to be patched, and assign the given tag.
+ */
+#define CODEPATCH_END(tag) \
+ 999: \
+ .section .codepatch, "a" ;\
+ .int (998b - KERNBASE) ;\
+ .short (999b - 998b) ;\
+ .short tag ;\
+ .previous
+
+#define CPTAG_STAC 1
+#define CPTAG_CLAC 2
+
+#endif /* _MACHINE_CODEPATCH_H_ */