aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndi Kleen <ak@linux.intel.com>2008-12-05 17:25:40 +0100
committerAndi Kleen <ak@linux.intel.com>2008-12-05 17:25:40 +0100
commitb25dc1e05981f18f9c392b12ccc768ebb71d358f (patch)
tree2ed612a859ba7ec70379354c5157f89172aed042
downloadmce-inject-b25dc1e05981f18f9c392b12ccc768ebb71d358f.tar.gz
Initial version from Andi Kleen
Signed-off-by: Andi Kleen <ak@linux.intel.com>
-rw-r--r--Makefile34
-rw-r--r--README11
-rw-r--r--SPEC65
-rw-r--r--inject.c129
-rw-r--r--inject.h4
-rw-r--r--mce.h82
-rw-r--r--mce.lex116
-rw-r--r--mce.y90
-rw-r--r--parser.h18
-rw-r--r--test/corrected9
-rw-r--r--test/fatal7
-rw-r--r--test/uncorrected4
-rw-r--r--util.c19
-rw-r--r--util.h12
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
diff --git a/README b/README
new file mode 100644
index 0000000..5db51ba
--- /dev/null
+++ b/README
@@ -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.
diff --git a/SPEC b/SPEC
new file mode 100644
index 0000000..5328c4e
--- /dev/null
+++ b/SPEC
@@ -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);
diff --git a/mce.h b/mce.h
new file mode 100644
index 0000000..7e8e9af
--- /dev/null
+++ b/mce.h
@@ -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
diff --git a/mce.lex b/mce.lex
new file mode 100644
index 0000000..bc5014e
--- /dev/null
+++ b/mce.lex
@@ -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();
+}
+
diff --git a/mce.y b/mce.y
new file mode 100644
index 0000000..6e8ff21
--- /dev/null
+++ b/mce.y
@@ -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
diff --git a/util.c b/util.c
new file mode 100644
index 0000000..17fc0af
--- /dev/null
+++ b/util.c
@@ -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);
+}
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..e2b1163
--- /dev/null
+++ b/util.h
@@ -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
+