diff options
author | Andi Kleen <ak@linux.intel.com> | 2008-12-05 17:25:40 +0100 |
---|---|---|
committer | Andi Kleen <ak@linux.intel.com> | 2008-12-05 17:25:40 +0100 |
commit | b25dc1e05981f18f9c392b12ccc768ebb71d358f (patch) | |
tree | 2ed612a859ba7ec70379354c5157f89172aed042 | |
download | mce-inject-b25dc1e05981f18f9c392b12ccc768ebb71d358f.tar.gz |
Initial version from Andi Kleen
Signed-off-by: Andi Kleen <ak@linux.intel.com>
-rw-r--r-- | Makefile | 34 | ||||
-rw-r--r-- | README | 11 | ||||
-rw-r--r-- | SPEC | 65 | ||||
-rw-r--r-- | inject.c | 129 | ||||
-rw-r--r-- | inject.h | 4 | ||||
-rw-r--r-- | mce.h | 82 | ||||
-rw-r--r-- | mce.lex | 116 | ||||
-rw-r--r-- | mce.y | 90 | ||||
-rw-r--r-- | parser.h | 18 | ||||
-rw-r--r-- | test/corrected | 9 | ||||
-rw-r--r-- | test/fatal | 7 | ||||
-rw-r--r-- | test/uncorrected | 4 | ||||
-rw-r--r-- | util.c | 19 | ||||
-rw-r--r-- | util.h | 12 |
14 files changed, 600 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..25fc789 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +CFLAGS := -g -Wall +LDFLAGS += -lpthread + +OBJ := mce.tab.o lex.yy.o inject.o util.o +GENSRC := mce.tab.c mce.tab.h lex.yy.c +SRC := inject.c util.c +CLEAN := ${OBJ} ${GENSRC} inject +DISTCLEAN := .depend .gdb_history + +.PHONY: clean depend + +inject: ${OBJ} + +lex.yy.c: mce.lex mce.tab.h + flex mce.lex + +mce.tab.c mce.tab.h: mce.y + bison -d mce.y + +clean: + rm -f ${CLEAN} + +distclean: clean + rm -f ${DISTCLEAN} *~ + +depend: .depend + +.depend: ${SRC} ${GENSRC} + ${CC} -MM -DDEPS_RUN -I. ${SRC} ${GENSRC} > .depend.X && \ + mv .depend.X .depend + +Makefile: .depend + +include .depend @@ -0,0 +1,11 @@ +mce-inject allows to inject machine check errors on the +software level into a running Linux kernel. + +mce-inject file .. + +See SPEC for the input language + +Some simple tests are in test/*. +Warning: some of them will panic the machine (this is intended) + +Requires a new Linux kernel with error injection patches. @@ -0,0 +1,65 @@ + +mce-inject allows to inject machine checks in a running kernel. + +Has to run as root. /dev/mcelog has to exist. + +mce-inject mce-file1 ... +mce-inject < mce-file + +Multiple files are processed in sequence + +Requires a kernel with machine check injection +support. The injection only happens on the software level and does +not simulate full machine check handling on the platform level. + +The machine checks to be injected are described in a input language similar +to the format the kernel outputs on a panic message (with a few extension). + +In general you should be able to pipe in a panic message to inject +that mce. + +See the IA32 SDM Vol3a chapter 14 for the details on fields. They correspond +to the machine check MSRs of the standard IA32 Machine check architecture +described there. + +The keywords are case-insensitive + +The machine check always starts with + +MCE + +or + +CPU number|broadcast [bank-number] + Machine check is injected on CPU xx or broadcasted to all CPUs + +BANK bank-number +STATUS {number|fatal|corrected|uncorrected|pcc|uc|val|en|over} +MCGSTATUS {number|mcip|ripv|eipv} +ADDR number +RIP number +MISC number + +CPU number : machine check exception : number BANK number : number + parsed for compatibility with kernel output. Use explicit statements + +TSC number + TSC state injected in machine check + +NOBROADCAST + Don't broadcast exceptions to all CPUs. Default is to broadcast + UC + + +multiple fields can be on a line + +number can be hex/octal/decimal in the usual C format. + +Multiple machine checks can be in a single file, each new one +starts with "CPU" or "MCE" + +For all missing fields reasonable default values are filled in +(hopefully) + +Comments start with # until the end of the line + diff --git a/inject.c b/inject.c new file mode 100644 index 0000000..0ab76bf --- /dev/null +++ b/inject.c @@ -0,0 +1,129 @@ +/* Inject machine checks into kernel for testing */ +#define _GNU_SOURCE 1 +#include <stdio.h> +#include <sys/fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <pthread.h> + +#include "mce.h" +#include "inject.h" +#include "parser.h" +#include "util.h" + +static void write_mce(int fd, struct mce *m) +{ + int n = write(fd, m, sizeof(struct mce)); + if (n <= 0) + err("Injecting mce on /dev/mcelog"); + if (n < sizeof(struct mce)) { + fprintf(stderr, "Short mce write %d: kernel does not match?\n", + n); + } +} + +struct thread { + struct thread *next; + pthread_t thr; + struct mce *m; + struct mce otherm; + int fd; +}; + +volatile int blocked; + +static void *injector(void *data) +{ + struct thread *t = (struct thread *)data; + + while (blocked == 0) + barrier(); + + write_mce(t->fd, t->m); + return NULL; +} + +/* Simulate machine check broadcast. */ +void broadcast_mce(int fd, struct mce *m) +{ + FILE *f = fopen("/proc/cpuinfo", "r"); + char *line = NULL; + size_t linesz = 0; + struct mce otherm; + struct thread *tlist = NULL; + + memset(&otherm, 0, sizeof(struct mce)); + // make sure to trigger exception on the secondaries + otherm.mcgstatus = m->mcgstatus & MCG_STATUS_MCIP; + otherm.status = m->status & MCI_STATUS_UC; + + if (!f) + err("opening of /proc/cpuinfo"); + + blocked = 1; + barrier(); + + while (getdelim(&line, &linesz, '\n', f) > 0) { + unsigned cpu; + if (sscanf(line, "processor : %u\n", &cpu) == 1) { + struct thread *t; + pthread_attr_t attr; + cpu_set_t aset; + + NEW(t); + t->next = tlist; + tlist = t; + t->m = cpu == m->cpu ? m : &t->otherm; + t->fd = fd; + t->otherm = otherm; + t->otherm.cpu = cpu; + + pthread_attr_init(&attr); + CPU_ZERO(&aset); + CPU_SET(cpu, &aset); + if (pthread_attr_setaffinity_np(&attr, CPU_SETSIZE, &aset) < 0) + err("pthread_attr_setaffinity"); + if (pthread_create(&t->thr, &attr, injector, t) < 0) + err("pthread_create"); + } + } + free(line); + fclose(f); + + /* could wait here for the threads to start up, but the kernel timeout should + be long enough to catch slow ones */ + + barrier(); + blocked = 0; + + while (tlist) { + struct thread *next = tlist->next; + pthread_join(tlist->thr, NULL); + free(tlist); + tlist = next; + } +} + +void submit_mce(struct mce *m) +{ + int inject_fd; + + inject_fd = open("/dev/mcelog", O_RDWR); + if (inject_fd < 0) + err("opening of /dev/mcelog"); + + if ((m->status & MCI_STATUS_UC) && !(mce_flags & MCE_NOBROADCAST)) { + /* broadcast */ + broadcast_mce(inject_fd, m); + } else { + write_mce(inject_fd, m); + } + close(inject_fd); +} + +void init_mce(struct mce *m) +{ + memset(m, 0, sizeof(struct mce)); +} + diff --git a/inject.h b/inject.h new file mode 100644 index 0000000..95e2982 --- /dev/null +++ b/inject.h @@ -0,0 +1,4 @@ +struct mce; + +void submit_mce(struct mce *m); +void init_mce(struct mce *m); @@ -0,0 +1,82 @@ +/* Taken from the v2.6.26 Linux 2.6.26-rc8 kernel source */ +#ifndef _ASM_X86_MCE_H +#define _ASM_X86_MCE_H + +#include <asm/ioctls.h> +#include <asm/types.h> + +/* + * Machine Check support for x86 + */ + +#define MCG_CTL_P (1ULL<<8) /* MCG_CAP register available */ + +#define MCG_STATUS_RIPV (1ULL<<0) /* restart ip valid */ +#define MCG_STATUS_EIPV (1ULL<<1) /* ip points to correct instruction */ +#define MCG_STATUS_MCIP (1ULL<<2) /* machine check in progress */ + +#define MCI_STATUS_VAL (1ULL<<63) /* valid error */ +#define MCI_STATUS_OVER (1ULL<<62) /* previous errors lost */ +#define MCI_STATUS_UC (1ULL<<61) /* uncorrected error */ +#define MCI_STATUS_EN (1ULL<<60) /* error enabled */ +#define MCI_STATUS_MISCV (1ULL<<59) /* misc error reg. valid */ +#define MCI_STATUS_ADDRV (1ULL<<58) /* addr reg. valid */ +#define MCI_STATUS_PCC (1ULL<<57) /* processor context corrupt */ + +/* Fields are zero when not available */ +struct mce { + __u64 status; + __u64 misc; + __u64 addr; + __u64 mcgstatus; + __u64 ip; + __u64 tsc; /* cpu time stamp counter */ + __u64 res1; /* for future extension */ + __u64 res2; /* dito. */ + __u8 cs; /* code segment */ + __u8 bank; /* machine check bank */ + __u8 cpu; /* cpu that raised the error */ + __u8 finished; /* entry is valid */ + __u32 pad; +}; + +/* + * This structure contains all data related to the MCE log. Also + * carries a signature to make it easier to find from external + * debugging tools. Each entry is only valid when its finished flag + * is set. + */ + +#define MCE_LOG_LEN 32 + +struct mce_log { + char signature[12]; /* "MACHINECHECK" */ + unsigned len; /* = MCE_LOG_LEN */ + unsigned next; + unsigned flags; + unsigned pad0; + struct mce entry[MCE_LOG_LEN]; +}; + +#define MCE_OVERFLOW 0 /* bit 0 in flags means overflow */ + +#define MCE_LOG_SIGNATURE "MACHINECHECK" + +#define MCE_GET_RECORD_LEN _IOR('M', 1, int) +#define MCE_GET_LOG_LEN _IOR('M', 2, int) +#define MCE_GETCLEAR_FLAGS _IOR('M', 3, int) + +/* Software defined banks */ +#define MCE_EXTENDED_BANK 128 +#define MCE_THERMAL_BANK MCE_EXTENDED_BANK + 0 + +#define K8_MCE_THRESHOLD_BASE (MCE_EXTENDED_BANK + 1) /* MCE_AMD */ +#define K8_MCE_THRESHOLD_BANK_0 (MCE_THRESHOLD_BASE + 0 * 9) +#define K8_MCE_THRESHOLD_BANK_1 (MCE_THRESHOLD_BASE + 1 * 9) +#define K8_MCE_THRESHOLD_BANK_2 (MCE_THRESHOLD_BASE + 2 * 9) +#define K8_MCE_THRESHOLD_BANK_3 (MCE_THRESHOLD_BASE + 3 * 9) +#define K8_MCE_THRESHOLD_BANK_4 (MCE_THRESHOLD_BASE + 4 * 9) +#define K8_MCE_THRESHOLD_BANK_5 (MCE_THRESHOLD_BASE + 5 * 9) +#define K8_MCE_THRESHOLD_DRAM_ECC (MCE_THRESHOLD_BANK_4 + 0) + +#endif @@ -0,0 +1,116 @@ +/* Scanner for the machine check grammar */ +%{ +#define _GNU_SOURCE 1 +#include <stdlib.h> +#include <string.h> + +#include "mce.h" +#include "parser.h" +#include "mce.tab.h" +#include "util.h" + +int yylineno; + +static int lookup_symbol(const char *); +%} + +%% + +#.*\n /* comment */; +\n ++yylineno; +0x[0-9a-fA-F]+ | +0[0-7]+ | +[0-9]+ yylval = strtoull(yytext, NULL, 0); return NUMBER; +[:{}<>] return yytext[0]; +[_a-zA-Z][_a-zA-Z0-9]* return lookup_symbol(yytext); +[ \t]+ /* white space */; +. yyerror("Unrecognized character '%s'", yytext); + +%% + +/* Keyword handling */ + +static struct key { + const char *name; + int tok; + u64 val; +} keys[] = { +#define KEY(x) { #x, x } +#define KEYVAL(x,v) { #x, x, v } + KEY(MCE), + KEY(STATUS), + KEY(RIP), + KEY(TSC), + KEY(ADDR), + KEY(MISC), + KEY(CPU), + KEY(BANK), + KEY(MCGSTATUS), + KEY(NOBROADCAST), + KEYVAL(CORRECTED, MCI_STATUS_VAL), // checkme + KEYVAL(UNCORRECTED, MCI_STATUS_UC|MCI_STATUS_EN), + KEYVAL(FATAL, MCI_STATUS_UC|MCI_STATUS_EN|MCI_STATUS_PCC), + KEY(MACHINE), + KEY(CHECK), + KEY(EXCEPTION), + KEYVAL(RIPV, MCG_STATUS_RIPV), + KEYVAL(EIPV, MCG_STATUS_EIPV), + KEYVAL(MCIP, MCG_STATUS_MCIP), + KEYVAL(VAL, MCI_STATUS_VAL), + KEYVAL(OVER, MCI_STATUS_OVER), + KEYVAL(UC, MCI_STATUS_UC), + KEYVAL(EN, MCI_STATUS_EN), + KEYVAL(PCC, MCI_STATUS_PCC), +}; + +static int cmp_key(const void *av, const void *bv) +{ + const struct key *a = av; + const struct key *b = bv; + return strcasecmp(a->name, b->name); +} + +static int lookup_symbol(const char *name) +{ + struct key *k; + struct key key; + key.name = name; + k = bsearch(&key, keys, ARRAY_SIZE(keys), sizeof(struct key), cmp_key); + if (k != NULL) { + yylval = k->val; + printf("got %s val %lx\n", k->name, k->val); + return k->tok; + } + printf("SYMBOL\n"); + return SYMBOL; +} + +static void init_lex(void) +{ + qsort(keys, ARRAY_SIZE(keys), sizeof(struct key), cmp_key); +} + +static char **argv; +char *filename = "<stdin>"; + +int yywrap(void) +{ + if (*argv == NULL) + return 1; + filename = *argv; + yyin = fopen(filename, "r"); + if (!yyin) + err(filename); + argv++; + return 0; +} + +int main(int ac, char **av) +{ + init_lex(); + argv = ++av; + if (*argv) + yywrap(); + return yyparse(); +} + @@ -0,0 +1,90 @@ +/* Grammar for machine check injection. Follows the format printed out + by the kernel on panics with some extensions. See SPEC. */ +%{ +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> + +#include "parser.h" +#include "mce.h" +#include "inject.h" + +static struct mce m; + +enum mceflags mce_flags; + +static void init(void); + +%} + +%token STATUS RIP TSC ADDR MISC CPU BANK MCGSTATUS NOBROADCAST +%token CORRECTED UNCORRECTED FATAL MCE +%token NUMBER +%token SYMBOL +%token MACHINE CHECK EXCEPTION + +%token RIPV EIPV MCIP +%token VAL OVER UC EN PCC + +%% + +input: /* empty */ + | input mce_start mce { submit_mce(&m); } ; + +mce_start: + | CPU NUMBER { init(); m.cpu = $2; } + | CPU NUMBER NUMBER { init(); m.cpu = $2; m.bank = $3; } + | MCE { init(); } + | CPU NUMBER ':' + MACHINE CHECK EXCEPTION ':' NUMBER BANK NUMBER ':' + NUMBER { init(); + m.cpu = $2; m.mcgstatus = $6; + m.bank = $8; m.status = $10; } + ; + +mce: STATUS status_list { m.status = $2; } + | MCGSTATUS mcgstatus_list { m.mcgstatus = $2; } + | BANK NUMBER { m.bank = $2; } + + | TSC NUMBER { m.tsc = $2; } + | RIP NUMBER { m.ip = $2; } + | RIP NUMBER ':' '<' NUMBER '>' '{' SYMBOL '}' + { m.ip = $5; m.cs = $2; } + | ADDR NUMBER { m.addr = $2; m.status |= MCI_STATUS_ADDRV; } + | MISC NUMBER { m.misc = $2; m.status |= MCI_STATUS_MISCV; } + | NOBROADCAST { mce_flags |= MCE_NOBROADCAST; } + ; + +mcgstatus_list: /* empty */ + | mcgstatus_list mcgstatus { $$ = $1 | $2; } + ; + +mcgstatus : RIPV | EIPV | MCIP | NUMBER ; + +status_list: /* empty */ { $$ = 0; } + | status_list status { $$ = $1 | $2; } + +status: UC | EN | VAL | OVER | PCC | NUMBER | CORRECTED | UNCORRECTED | + FATAL + ; + +%% + +static void init(void) +{ + init_mce(&m); + mce_flags = 0; +} + +void yyerror(char const *msg, ...) +{ + va_list ap; + va_start(ap, msg); + fprintf(stderr, "%s:%d: ", filename, yylineno); + vfprintf(stderr, msg, ap); + fputc('\n', stderr); + va_end(ap); + exit(1); +} + diff --git a/parser.h b/parser.h new file mode 100644 index 0000000..0e5f55a --- /dev/null +++ b/parser.h @@ -0,0 +1,18 @@ + +typedef unsigned long long u64; + +#define YYSTYPE u64 + + +extern void yyerror(const char *fmt, ...); +extern int yylineno; +extern int yylex(void); +extern int yyparse(void); +extern char *filename; + +enum mceflags { + MCE_NOBROADCAST = (1 << 0), +}; + +extern enum mceflags mce_flags; + diff --git a/test/corrected b/test/corrected new file mode 100644 index 0000000..895d08e --- /dev/null +++ b/test/corrected @@ -0,0 +1,9 @@ +# +# log two corrected machine checks +CPU 0 BANK 1 +STATUS CORRECTED +ADDR 0xabcd +# +CPU 1 BANK 2 +STATUS CORRECTED +ADDR 0x1234 diff --git a/test/fatal b/test/fatal new file mode 100644 index 0000000..ef3b56b --- /dev/null +++ b/test/fatal @@ -0,0 +1,7 @@ +# WARNING +# this will panic your machine! +# don't try casually +CPU 0 +STATUS fatal +RIP 12343434 + diff --git a/test/uncorrected b/test/uncorrected new file mode 100644 index 0000000..d31a93e --- /dev/null +++ b/test/uncorrected @@ -0,0 +1,4 @@ +# this should just kill the process +STATUS uncorrected EIPV +ADDR 0x1234 +RIP 0xdeadbabe @@ -0,0 +1,19 @@ +#include <stdlib.h> +#include <stdio.h> +#include "util.h" + +void *xalloc(size_t sz) +{ + void *p = calloc(sz, 1); + if (!p) { + fprintf(stderr, "Out of virtual memory\n"); + exit(1); + } + return p; +} + +void err(const char *msg) +{ + perror(msg); + exit(1); +} @@ -0,0 +1,12 @@ +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(*(x))) +void err(const char *msg); + +#define NEW(x) ((x) = xalloc(sizeof(*(x)))) +void *xalloc(size_t sz); + +#ifdef __GNUC__ +#define barrier() asm volatile("" ::: "memory") +#else +#define barrier() do {} while(0) +#endif + |