#include <sys/param.h>
#include <sys/systm.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 {
vaddr_t addr;
uint16_t len;
uint16_t tag;
uint32_t _padding;
};
CTASSERT(sizeof(struct codepatch) % 8 == 0);
extern struct codepatch codepatch_begin;
extern struct codepatch codepatch_end;
extern char __cptext_start[];
extern char __cptext_end[];
enum op_type { OP_CALL, OP_JMP };
__cptext void codepatch_control_flow(uint16_t _tag, void *_func,
enum op_type _type);
void
codepatch_fill_nop(void *caddr, uint16_t len)
{
unsigned char *addr = caddr;
uint16_t nop_len;
while (len > 0) {
nop_len = len < 127 ? len : 127;
switch (nop_len) {
case 1:
addr[0] = 0x90;
break;
case 2:
addr[0] = 0x66;
addr[1] = 0x90;
break;
default:
addr[0] = 0xEB;
addr[1] = nop_len - 2;
memset(addr + 2, 0xCC, nop_len - 2);
break;
}
addr += nop_len;
len -= nop_len;
}
}
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)
return;
pmap_kremove(nva, 2 * PAGE_SIZE);
km_free((void *)nva, 2 * PAGE_SIZE, &kv_any, &kp_none);
}
void
codepatch_nop(uint16_t tag)
{
struct codepatch *patch;
unsigned char *rwaddr;
vaddr_t rwmap = 0;
int i = 0;
DBGPRINT("patching tag %u", tag);
for (patch = &codepatch_begin; patch < &codepatch_end; patch++) {
if (patch->tag != tag)
continue;
rwaddr = codepatch_maprw(&rwmap, patch->addr);
codepatch_fill_nop(rwaddr, patch->len);
i++;
}
codepatch_unmaprw(rwmap);
DBGPRINT("patched %d places", i);
}
void
codepatch_replace(uint16_t tag, const void *code, size_t len)
{
struct codepatch *patch;
unsigned char *rwaddr;
vaddr_t 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;
if (len > patch->len) {
panic("%s: can't replace len %u with %zu at %#lx",
__func__, patch->len, len, patch->addr);
}
rwaddr = codepatch_maprw(&rwmap, patch->addr);
memcpy(rwaddr, code, len);
codepatch_fill_nop(rwaddr + len, patch->len - len);
i++;
}
codepatch_unmaprw(rwmap);
DBGPRINT("patched %d places", i);
}
void
codepatch_call(uint16_t tag, void *func)
{
codepatch_control_flow(tag, func, OP_CALL);
}
void
codepatch_jmp(uint16_t tag, void *func)
{
codepatch_control_flow(tag, func, OP_JMP);
}
void
codepatch_control_flow(uint16_t tag, void *func, enum op_type type)
{
struct codepatch *patch;
unsigned char *rwaddr;
int32_t offset;
int i = 0;
vaddr_t rwmap = 0;
const char *op = type == OP_JMP ? "jmp" : "call";
char opcode = type == OP_JMP ? 0xe9
: 0xe8 ;
DBGPRINT("patching tag %u with %s %p", tag, op, func);
for (patch = &codepatch_begin; patch < &codepatch_end; patch++) {
if (patch->tag != tag)
continue;
if (patch->len < 5)
panic("%s: can't replace len %u with %s at %#lx",
__func__, patch->len, op, patch->addr);
offset = (vaddr_t)func - (patch->addr + 5);
rwaddr = codepatch_maprw(&rwmap, patch->addr);
rwaddr[0] = opcode;
memcpy(rwaddr + 1, &offset, sizeof(offset));
if (type == OP_CALL)
codepatch_fill_nop(rwaddr + 5, patch->len - 5);
else
memset(rwaddr + 5, 0xCC , patch->len - 5);
i++;
}
codepatch_unmaprw(rwmap);
DBGPRINT("patched %d places", i);
}
void
codepatch_disable(void)
{
size_t size = round_page(__cptext_end - __cptext_start);
KASSERT(size > 0);
pmap_kremove((vaddr_t)__cptext_start, size);
pmap_update(pmap_kernel());
DBGPRINT("%s: Unmapped %#zx bytes\n", __func__, size);
}