linux/arch/x86/mm/pf_in.c

490 lines
9.9 KiB
C
Raw Normal View History

/*
* Fault Injection Test harness (FI)
* Copyright (C) Intel Crop.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*
*/
x86: mmiotrace full patch, preview 1 kmmio.c handles the list of mmio probes with callbacks, list of traced pages, and attaching into the page fault handler and die notifier. It arms, traps and disarms the given pages, this is the core of mmiotrace. mmio-mod.c is a user interface, hooking into ioremap functions and registering the mmio probes. It also decodes the required information from trapped mmio accesses via the pre and post callbacks in each probe. Currently, hooking into ioremap functions works by redefining the symbols of the target (binary) kernel module, so that it calls the traced versions of the functions. The most notable changes done since the last discussion are: - kmmio.c is a built-in, not part of the module - direct call from fault.c to kmmio.c, removing all dynamic hooks - prepare for unregistering probes at any time - make kmmio re-initializable and accessible to more than one user - rewrite kmmio locking to remove all spinlocks from page fault path Can I abuse call_rcu() like I do in kmmio.c:unregister_kmmio_probe() or is there a better way? The function called via call_rcu() itself calls call_rcu() again, will this work or break? There I need a second grace period for RCU after the first grace period for page faults. Mmiotrace itself (mmio-mod.c) is still a module, I am going to attack that next. At some point I will start looking into how to make mmiotrace a tracer component of ftrace (thanks for the hint, Ingo). Ftrace should make the user space part of mmiotracing as simple as 'cat /debug/trace/mmio > dump.txt'. Signed-off-by: Ingo Molnar <mingo@elte.hu> Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
2008-05-13 03:20:57 +08:00
/* Id: pf_in.c,v 1.1.1.1 2002/11/12 05:56:32 brlock Exp
* Copyright by Intel Crop., 2002
* Louis Zhuang (louis.zhuang@intel.com)
*
* Bjorn Steinbrink (B.Steinbrink@gmx.de), 2007
*/
#include <linux/module.h>
#include <linux/ptrace.h> /* struct pt_regs */
#include "pf_in.h"
#ifdef __i386__
/* IA32 Manual 3, 2-1 */
static unsigned char prefix_codes[] = {
0xF0, 0xF2, 0xF3, 0x2E, 0x36, 0x3E, 0x26, 0x64,
0x65, 0x2E, 0x3E, 0x66, 0x67
};
/* IA32 Manual 3, 3-432*/
static unsigned int reg_rop[] = {
0x8A, 0x8B, 0xB60F, 0xB70F, 0xBE0F, 0xBF0F
};
static unsigned int reg_wop[] = { 0x88, 0x89 };
static unsigned int imm_wop[] = { 0xC6, 0xC7 };
/* IA32 Manual 3, 3-432*/
static unsigned int rw8[] = { 0x88, 0x8A, 0xC6 };
static unsigned int rw32[] = {
0x89, 0x8B, 0xC7, 0xB60F, 0xB70F, 0xBE0F, 0xBF0F
};
static unsigned int mw8[] = { 0x88, 0x8A, 0xC6, 0xB60F, 0xBE0F };
static unsigned int mw16[] = { 0xB70F, 0xBF0F };
static unsigned int mw32[] = { 0x89, 0x8B, 0xC7 };
static unsigned int mw64[] = {};
#else /* not __i386__ */
static unsigned char prefix_codes[] = {
0x66, 0x67, 0x2E, 0x3E, 0x26, 0x64, 0x65, 0x36,
0xF0, 0xF3, 0xF2,
/* REX Prefixes */
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f
};
/* AMD64 Manual 3, Appendix A*/
static unsigned int reg_rop[] = {
0x8A, 0x8B, 0xB60F, 0xB70F, 0xBE0F, 0xBF0F
};
static unsigned int reg_wop[] = { 0x88, 0x89 };
static unsigned int imm_wop[] = { 0xC6, 0xC7 };
static unsigned int rw8[] = { 0xC6, 0x88, 0x8A };
static unsigned int rw32[] = {
0xC7, 0x89, 0x8B, 0xB60F, 0xB70F, 0xBE0F, 0xBF0F
};
/* 8 bit only */
static unsigned int mw8[] = { 0xC6, 0x88, 0x8A, 0xB60F, 0xBE0F };
/* 16 bit only */
static unsigned int mw16[] = { 0xB70F, 0xBF0F };
/* 16 or 32 bit */
static unsigned int mw32[] = { 0xC7 };
/* 16, 32 or 64 bit */
static unsigned int mw64[] = { 0x89, 0x8B };
#endif /* not __i386__ */
static int skip_prefix(unsigned char *addr, int *shorted, int *enlarged,
int *rexr)
{
int i;
unsigned char *p = addr;
*shorted = 0;
*enlarged = 0;
*rexr = 0;
restart:
for (i = 0; i < ARRAY_SIZE(prefix_codes); i++) {
if (*p == prefix_codes[i]) {
if (*p == 0x66)
*shorted = 1;
#ifdef __amd64__
if ((*p & 0xf8) == 0x48)
*enlarged = 1;
if ((*p & 0xf4) == 0x44)
*rexr = 1;
#endif
p++;
goto restart;
}
}
return (p - addr);
}
static int get_opcode(unsigned char *addr, unsigned int *opcode)
{
int len;
if (*addr == 0x0F) {
/* 0x0F is extension instruction */
*opcode = *(unsigned short *)addr;
len = 2;
} else {
*opcode = *addr;
len = 1;
}
return len;
}
#define CHECK_OP_TYPE(opcode, array, type) \
for (i = 0; i < ARRAY_SIZE(array); i++) { \
if (array[i] == opcode) { \
rv = type; \
goto exit; \
} \
}
enum reason_type get_ins_type(unsigned long ins_addr)
{
unsigned int opcode;
unsigned char *p;
int shorted, enlarged, rexr;
int i;
enum reason_type rv = OTHERS;
p = (unsigned char *)ins_addr;
p += skip_prefix(p, &shorted, &enlarged, &rexr);
p += get_opcode(p, &opcode);
CHECK_OP_TYPE(opcode, reg_rop, REG_READ);
CHECK_OP_TYPE(opcode, reg_wop, REG_WRITE);
CHECK_OP_TYPE(opcode, imm_wop, IMM_WRITE);
exit:
return rv;
}
#undef CHECK_OP_TYPE
static unsigned int get_ins_reg_width(unsigned long ins_addr)
{
unsigned int opcode;
unsigned char *p;
int i, shorted, enlarged, rexr;
p = (unsigned char *)ins_addr;
p += skip_prefix(p, &shorted, &enlarged, &rexr);
p += get_opcode(p, &opcode);
for (i = 0; i < ARRAY_SIZE(rw8); i++)
if (rw8[i] == opcode)
return 1;
for (i = 0; i < ARRAY_SIZE(rw32); i++)
if (rw32[i] == opcode)
return (shorted ? 2 : (enlarged ? 8 : 4));
printk(KERN_ERR "mmiotrace: Unknown opcode 0x%02x\n", opcode);
return 0;
}
unsigned int get_ins_mem_width(unsigned long ins_addr)
{
unsigned int opcode;
unsigned char *p;
int i, shorted, enlarged, rexr;
p = (unsigned char *)ins_addr;
p += skip_prefix(p, &shorted, &enlarged, &rexr);
p += get_opcode(p, &opcode);
for (i = 0; i < ARRAY_SIZE(mw8); i++)
if (mw8[i] == opcode)
return 1;
for (i = 0; i < ARRAY_SIZE(mw16); i++)
if (mw16[i] == opcode)
return 2;
for (i = 0; i < ARRAY_SIZE(mw32); i++)
if (mw32[i] == opcode)
return shorted ? 2 : 4;
for (i = 0; i < ARRAY_SIZE(mw64); i++)
if (mw64[i] == opcode)
return shorted ? 2 : (enlarged ? 8 : 4);
printk(KERN_ERR "mmiotrace: Unknown opcode 0x%02x\n", opcode);
return 0;
}
/*
* Define register ident in mod/rm byte.
* Note: these are NOT the same as in ptrace-abi.h.
*/
enum {
arg_AL = 0,
arg_CL = 1,
arg_DL = 2,
arg_BL = 3,
arg_AH = 4,
arg_CH = 5,
arg_DH = 6,
arg_BH = 7,
arg_AX = 0,
arg_CX = 1,
arg_DX = 2,
arg_BX = 3,
arg_SP = 4,
arg_BP = 5,
arg_SI = 6,
arg_DI = 7,
#ifdef __amd64__
arg_R8 = 8,
arg_R9 = 9,
arg_R10 = 10,
arg_R11 = 11,
arg_R12 = 12,
arg_R13 = 13,
arg_R14 = 14,
arg_R15 = 15
#endif
};
static unsigned char *get_reg_w8(int no, struct pt_regs *regs)
{
unsigned char *rv = NULL;
switch (no) {
case arg_AL:
rv = (unsigned char *)&regs->ax;
break;
case arg_BL:
rv = (unsigned char *)&regs->bx;
break;
case arg_CL:
rv = (unsigned char *)&regs->cx;
break;
case arg_DL:
rv = (unsigned char *)&regs->dx;
break;
case arg_AH:
rv = 1 + (unsigned char *)&regs->ax;
break;
case arg_BH:
rv = 1 + (unsigned char *)&regs->bx;
break;
case arg_CH:
rv = 1 + (unsigned char *)&regs->cx;
break;
case arg_DH:
rv = 1 + (unsigned char *)&regs->dx;
break;
#ifdef __amd64__
case arg_R8:
rv = (unsigned char *)&regs->r8;
break;
case arg_R9:
rv = (unsigned char *)&regs->r9;
break;
case arg_R10:
rv = (unsigned char *)&regs->r10;
break;
case arg_R11:
rv = (unsigned char *)&regs->r11;
break;
case arg_R12:
rv = (unsigned char *)&regs->r12;
break;
case arg_R13:
rv = (unsigned char *)&regs->r13;
break;
case arg_R14:
rv = (unsigned char *)&regs->r14;
break;
case arg_R15:
rv = (unsigned char *)&regs->r15;
break;
#endif
default:
printk(KERN_ERR "mmiotrace: Error reg no# %d\n", no);
break;
}
return rv;
}
static unsigned long *get_reg_w32(int no, struct pt_regs *regs)
{
unsigned long *rv = NULL;
switch (no) {
case arg_AX:
rv = &regs->ax;
break;
case arg_BX:
rv = &regs->bx;
break;
case arg_CX:
rv = &regs->cx;
break;
case arg_DX:
rv = &regs->dx;
break;
case arg_SP:
rv = &regs->sp;
break;
case arg_BP:
rv = &regs->bp;
break;
case arg_SI:
rv = &regs->si;
break;
case arg_DI:
rv = &regs->di;
break;
#ifdef __amd64__
case arg_R8:
rv = &regs->r8;
break;
case arg_R9:
rv = &regs->r9;
break;
case arg_R10:
rv = &regs->r10;
break;
case arg_R11:
rv = &regs->r11;
break;
case arg_R12:
rv = &regs->r12;
break;
case arg_R13:
rv = &regs->r13;
break;
case arg_R14:
rv = &regs->r14;
break;
case arg_R15:
rv = &regs->r15;
break;
#endif
default:
printk(KERN_ERR "mmiotrace: Error reg no# %d\n", no);
}
return rv;
}
unsigned long get_ins_reg_val(unsigned long ins_addr, struct pt_regs *regs)
{
unsigned int opcode;
unsigned char mod_rm;
int reg;
unsigned char *p;
int i, shorted, enlarged, rexr;
unsigned long rv;
p = (unsigned char *)ins_addr;
p += skip_prefix(p, &shorted, &enlarged, &rexr);
p += get_opcode(p, &opcode);
for (i = 0; i < ARRAY_SIZE(reg_rop); i++)
if (reg_rop[i] == opcode) {
rv = REG_READ;
goto do_work;
}
for (i = 0; i < ARRAY_SIZE(reg_wop); i++)
if (reg_wop[i] == opcode) {
rv = REG_WRITE;
goto do_work;
}
printk(KERN_ERR "mmiotrace: Not a register instruction, opcode "
"0x%02x\n", opcode);
goto err;
do_work:
mod_rm = *p;
reg = ((mod_rm >> 3) & 0x7) | (rexr << 3);
switch (get_ins_reg_width(ins_addr)) {
case 1:
return *get_reg_w8(reg, regs);
case 2:
return *(unsigned short *)get_reg_w32(reg, regs);
case 4:
return *(unsigned int *)get_reg_w32(reg, regs);
#ifdef __amd64__
case 8:
return *(unsigned long *)get_reg_w32(reg, regs);
#endif
default:
printk(KERN_ERR "mmiotrace: Error width# %d\n", reg);
}
err:
return 0;
}
unsigned long get_ins_imm_val(unsigned long ins_addr)
{
unsigned int opcode;
unsigned char mod_rm;
unsigned char mod;
unsigned char *p;
int i, shorted, enlarged, rexr;
unsigned long rv;
p = (unsigned char *)ins_addr;
p += skip_prefix(p, &shorted, &enlarged, &rexr);
p += get_opcode(p, &opcode);
for (i = 0; i < ARRAY_SIZE(imm_wop); i++)
if (imm_wop[i] == opcode) {
rv = IMM_WRITE;
goto do_work;
}
printk(KERN_ERR "mmiotrace: Not an immediate instruction, opcode "
"0x%02x\n", opcode);
goto err;
do_work:
mod_rm = *p;
mod = mod_rm >> 6;
p++;
switch (mod) {
case 0:
/* if r/m is 5 we have a 32 disp (IA32 Manual 3, Table 2-2) */
/* AMD64: XXX Check for address size prefix? */
if ((mod_rm & 0x7) == 0x5)
p += 4;
break;
case 1:
p += 1;
break;
case 2:
p += 4;
break;
case 3:
default:
printk(KERN_ERR "mmiotrace: not a memory access instruction "
"at 0x%lx, rm_mod=0x%02x\n",
ins_addr, mod_rm);
}
switch (get_ins_reg_width(ins_addr)) {
case 1:
return *(unsigned char *)p;
case 2:
return *(unsigned short *)p;
case 4:
return *(unsigned int *)p;
#ifdef __amd64__
case 8:
return *(unsigned long *)p;
#endif
default:
printk(KERN_ERR "mmiotrace: Error: width.\n");
}
err:
return 0;
}