diff --git a/Makefile b/Makefile
index b6e8759..f5eccc7 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,8 @@ libcflat := lib/libcflat.a
 cflatobjs := \
 	lib/panic.o \
 	lib/printf.o \
-	lib/string.o
+	lib/string.o \
+	lib/report.o
 cflatobjs += lib/argv.o
 
 #include architecure specific make rules
diff --git a/lib/libcflat.h b/lib/libcflat.h
index fadc33d..f734fde 100644
--- a/lib/libcflat.h
+++ b/lib/libcflat.h
@@ -45,6 +45,7 @@ extern char *strcat(char *dest, const char *src);
 extern int strcmp(const char *a, const char *b);
 
 extern int printf(const char *fmt, ...);
+extern int snprintf(char *buf, int size, const char *fmt, ...);
 extern int vsnprintf(char *buf, int size, const char *fmt, va_list va);
 
 extern void puts(const char *s);
@@ -58,4 +59,7 @@ extern long atol(const char *ptr);
 #define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
 
 #define NULL ((void *)0UL)
+
+void report(const char *msg_fmt, bool pass, ...);
+int report_summary(void);
 #endif
diff --git a/lib/report.c b/lib/report.c
new file mode 100644
index 0000000..ff562a1
--- /dev/null
+++ b/lib/report.c
@@ -0,0 +1,36 @@
+/*
+ * Test result reporting
+ *
+ * Copyright (c) Siemens AG, 2014
+ *
+ * Authors:
+ *  Jan Kiszka <jan.kiszka@siemens.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.
+ */
+
+#include "libcflat.h"
+
+static unsigned int tests, failures;
+
+void report(const char *msg_fmt, bool pass, ...)
+{
+	char buf[2000];
+	va_list va;
+
+	tests++;
+	printf("%s: ", pass ? "PASS" : "FAIL");
+	va_start(va, pass);
+	vsnprintf(buf, sizeof(buf), msg_fmt, va);
+	va_end(va);
+	puts(buf);
+	puts("\n");
+	if (!pass)
+		failures++;
+}
+
+int report_summary(void)
+{
+	printf("\nSUMMARY: %d tests, %d failures\n", tests, failures);
+	return failures > 0 ? 1 : 0;
+}
diff --git a/x86/apic.c b/x86/apic.c
index 8febfa2..487c248 100644
--- a/x86/apic.c
+++ b/x86/apic.c
@@ -6,17 +6,6 @@
 #include "isr.h"
 #include "msr.h"
 
-static int g_fail;
-static int g_tests;
-
-static void report(const char *msg, int pass)
-{
-    ++g_tests;
-    printf("%s: %s\n", msg, (pass ? "PASS" : "FAIL"));
-    if (!pass)
-        ++g_fail;
-}
-
 static void test_lapic_existence(void)
 {
     u32 lvr;
@@ -403,7 +392,5 @@ int main()
 
     test_tsc_deadline_timer();
 
-    printf("\nsummary: %d tests, %d failures\n", g_tests, g_fail);
-
-    return g_fail != 0;
+    return report_summary();
 }
diff --git a/x86/emulator.c b/x86/emulator.c
index b70e540..2e25dd8 100644
--- a/x86/emulator.c
+++ b/x86/emulator.c
@@ -7,8 +7,6 @@
 #define memset __builtin_memset
 #define TESTDEV_IO_PORT 0xe0
 
-int fails, tests;
-
 static int exceptions;
 
 struct regs {
@@ -25,17 +23,6 @@ struct insn_desc {
 	size_t len;
 };
 
-void report(const char *name, int result)
-{
-	++tests;
-	if (result)
-		printf("PASS: %s\n", name);
-	else {
-		printf("FAIL: %s\n", name);
-		++fails;
-	}
-}
-
 static char st1[] = "abcdefghijklmnop";
 
 void test_stringio()
@@ -1022,6 +1009,5 @@ int main()
 
 	test_string_io_mmio(mem);
 
-	printf("\nSUMMARY: %d tests, %d failures\n", tests, fails);
-	return fails ? 1 : 0;
+	return report_summary();
 }
diff --git a/x86/eventinj.c b/x86/eventinj.c
index 9d4392c..a218aaf 100644
--- a/x86/eventinj.c
+++ b/x86/eventinj.c
@@ -12,9 +12,6 @@
 #  define R "e"
 #endif
 
-static int g_fail;
-static int g_tests;
-
 static inline void io_delay(void)
 {
 }
@@ -24,14 +21,6 @@ static inline void outl(int addr, int val)
         asm volatile ("outl %1, %w0" : : "d" (addr), "a" (val));
 }
 
-static void report(const char *msg, int pass)
-{
-    ++g_tests;
-    printf("%s: %s\n", msg, (pass ? "PASS" : "FAIL"));
-    if (!pass)
-        ++g_fail;
-}
-
 void apic_self_ipi(u8 v)
 {
 	apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED |
@@ -416,7 +405,5 @@ int main()
 	printf("After int 33 with shadowed stack\n");
 	report("int 33 with shadowed stack", test_count == 1);
 
-	printf("\nsummary: %d tests, %d failures\n", g_tests, g_fail);
-
-	return g_fail != 0;
+	return report_summary();
 }
diff --git a/x86/idt_test.c b/x86/idt_test.c
index 2d2e0c2..ecb76bb 100644
--- a/x86/idt_test.c
+++ b/x86/idt_test.c
@@ -21,19 +21,6 @@ int test_gp(void)
     return exception_vector();
 }
 
-static int nr_fail, nr_test;
-
-static void report(int cond, const char *name)
-{
-    ++nr_test;
-    if (!cond) {
-        ++nr_fail;
-        printf("%s: FAIL\n", name);
-    } else {
-        printf("%s: PASS\n", name);
-    }
-}
-
 int main(void)
 {
     int r;
@@ -41,9 +28,9 @@ int main(void)
     printf("Starting IDT test\n");
     setup_idt();
     r = test_gp();
-    report(r == GP_VECTOR, "Testing #GP");
+    report("Testing #GP", r == GP_VECTOR);
     r = test_ud2();
-    report(r == UD_VECTOR, "Testing #UD");
-    printf("%d failures of %d tests\n", nr_fail, nr_test);
-    return !nr_fail ? 0 : 1;
+    report("Testing #UD", r == UD_VECTOR);
+
+    return report_summary();
 }
diff --git a/x86/msr.c b/x86/msr.c
index de7573d..db08a8d 100644
--- a/x86/msr.c
+++ b/x86/msr.c
@@ -80,17 +80,6 @@ static int find_msr_info(int msr_index)
     return -1;
 }
 
-
-int nr_passed, nr_tests;
-
-static void report(const char *name, int passed)
-{
-	++nr_tests;
-	if (passed)
-		++nr_passed;
-	printf("%s: %s\n", name, passed ? "PASS" : "FAIL");
-}
-
 static void test_msr_rw(int msr_index, unsigned long long input, unsigned long long expected)
 {
     unsigned long long r = 0;
@@ -142,8 +131,6 @@ int main(int ac, char **av)
 
     test_syscall_lazy_load();
 
-    printf("%d tests, %d failures\n", nr_tests, nr_tests - nr_passed);
-
-    return nr_passed == nr_tests ? 0 : 1;
+    return report_summary();
 }
 
diff --git a/x86/pcid.c b/x86/pcid.c
index 8bfeba2..45adfd5 100644
--- a/x86/pcid.c
+++ b/x86/pcid.c
@@ -4,8 +4,6 @@
 #include "processor.h"
 #include "desc.h"
 
-int nr_passed, nr_tests;
-
 #define X86_FEATURE_PCID       (1 << 17)
 #define X86_FEATURE_INVPCID    (1 << 10)
 
@@ -22,14 +20,6 @@ struct invpcid_desc {
     unsigned long addr : 64;
 };
 
-static void report(const char *name, int passed)
-{
-    ++nr_tests;
-    if (passed)
-        ++nr_passed;
-    printf("%s: %s\n", name, passed ? "PASS" : "FAIL");
-}
-
 int write_cr0_checking(unsigned long val)
 {
     asm volatile(ASM_TRY("1f")
@@ -180,7 +170,5 @@ int main(int ac, char **av)
     else
         test_invpcid_disabled();
 
-    printf("%d tests, %d failures\n", nr_tests, nr_tests - nr_passed);
-
-    return nr_passed == nr_tests ? 0 : 1;
+    return report_summary();
 }
diff --git a/x86/pmu.c b/x86/pmu.c
index 42b5a59..5c85146 100644
--- a/x86/pmu.c
+++ b/x86/pmu.c
@@ -92,7 +92,6 @@ struct pmu_event {
 };
 
 static int num_counters;
-static int tests, failures;
 
 char *buf;
 
@@ -211,13 +210,6 @@ static void measure(pmu_counter_t *evt, int count)
 		stop_event(&evt[i]);
 }
 
-static void report(const char *name, int n, bool pass)
-{
-    printf("%s: pmu %s-%d\n", pass ? "PASS" : "FAIL", name, n);
-    tests += 1;
-    failures += !pass;
-}
-
 static bool verify_event(uint64_t count, struct pmu_event *e)
 {
 	// printf("%lld >= %lld <= %lld\n", e->min, count, e->max);
@@ -236,12 +228,14 @@ static void check_gp_counter(struct pmu_event *evt)
 		.ctr = MSR_IA32_PERFCTR0,
 		.config = EVNTSEL_OS | EVNTSEL_USR | evt->unit_sel,
 	};
+	char fmt[100];
 	int i;
 
 	for (i = 0; i < num_counters; i++, cnt.ctr++) {
 		cnt.count = 0;
 		measure(&cnt, 1);
-		report(evt->name, i, verify_event(cnt.count, evt));
+		snprintf(fmt, sizeof(fmt), "%s-%%d", evt->name);
+		report(fmt, verify_event(cnt.count, evt), i);
 	}
 }
 
@@ -268,7 +262,7 @@ static void check_fixed_counters(void)
 		cnt.count = 0;
 		cnt.ctr = fixed_events[i].unit_sel;
 		measure(&cnt, 1);
-		report("fixed", i, verify_event(cnt.count, &fixed_events[i]));
+		report("fixed-%d", verify_event(cnt.count, &fixed_events[i]), i);
 	}
 }
 
@@ -299,7 +293,7 @@ static void check_counters_many(void)
 		if (!verify_counter(&cnt[i]))
 			break;
 
-	report("all counters", 0, i == n);
+	report("all counters", i == n);
 }
 
 static void check_counter_overflow(void)
@@ -329,13 +323,13 @@ static void check_counter_overflow(void)
 		idx = event_to_global_idx(&cnt);
 		cnt.count = 1 - count;
 		measure(&cnt, 1);
-		report("overflow", i, cnt.count == 1);
+		report("overflow-%d", cnt.count == 1, i);
 		status = rdmsr(MSR_CORE_PERF_GLOBAL_STATUS);
-		report("overflow status", i, status & (1ull << idx));
+		report("overflow status-%d", status & (1ull << idx), i);
 		wrmsr(MSR_CORE_PERF_GLOBAL_OVF_CTRL, status);
 		status = rdmsr(MSR_CORE_PERF_GLOBAL_STATUS);
-		report("overflow status clear", i, !(status & (1ull << idx)));
-		report("overflow irq", i, check_irq() == (i % 2));
+		report("overflow status clear-%d", !(status & (1ull << idx)), i);
+		report("overflow irq-%d", check_irq() == (i % 2), i);
 	}
 }
 
@@ -348,7 +342,7 @@ static void check_gp_counter_cmask(void)
 	};
 	cnt.config |= (0x2 << EVNTSEL_CMASK_SHIFT);
 	measure(&cnt, 1);
-	report("cmask", 0, cnt.count < gp_events[1].min);
+	report("cmask", cnt.count < gp_events[1].min);
 }
 
 static void check_rdpmc(void)
@@ -360,15 +354,15 @@ static void check_rdpmc(void)
 		uint64_t x = (val & 0xffffffff) |
 			((1ull << (eax.split.bit_width - 32)) - 1) << 32;
 		wrmsr(MSR_IA32_PERFCTR0 + i, val);
-		report("rdpmc", i, rdpmc(i) == x);
-		report("rdpmc fast", i, rdpmc(i | (1<<31)) == (u32)val);
+		report("rdpmc-%d", rdpmc(i) == x, i);
+		report("rdpmc fast-%d", rdpmc(i | (1<<31)) == (u32)val, i);
 	}
 	for (i = 0; i < edx.split.num_counters_fixed; i++) {
 		uint64_t x = (val & 0xffffffff) |
 			((1ull << (edx.split.bit_width_fixed - 32)) - 1) << 32;
 		wrmsr(MSR_CORE_PERF_FIXED_CTR0 + i, val);
-		report("rdpmc fixed", i, rdpmc(i | (1 << 30)) == x);
-		report("rdpmc fixed fast", i, rdpmc(i | (3<<30)) == (u32)val);
+		report("rdpmc fixed-%d", rdpmc(i | (1 << 30)) == x, i);
+		report("rdpmc fixed fast-%d", rdpmc(i | (3<<30)) == (u32)val, i);
 	}
 }
 
@@ -409,6 +403,5 @@ int main(int ac, char **av)
 	check_counter_overflow();
 	check_gp_counter_cmask();
 
-	printf("\n%d tests, %d failures\n", tests, failures);
-	return !failures ? 0 : 1;
+	return report_summary();
 }
diff --git a/x86/taskswitch2.c b/x86/taskswitch2.c
index 6573696..3c8418e 100644
--- a/x86/taskswitch2.c
+++ b/x86/taskswitch2.c
@@ -18,21 +18,10 @@ static volatile unsigned int test_divider;
 static char *fault_addr;
 static ulong fault_phys;
 
-static int g_fail;
-static int g_tests;
-
 static inline void io_delay(void)
 {
 }
 
-static void report(const char *msg, int pass)
-{
-    ++g_tests;
-    printf("%s: %s\n", msg, (pass ? "PASS" : "FAIL"));
-    if (!pass)
-        ++g_fail;
-}
-
 static void nmi_tss(void)
 {
 start:
@@ -273,7 +262,5 @@ int main()
 	test_kernel_mode_int();
 	test_vm86_switch();
 
-	printf("\nsummary: %d tests, %d failures\n", g_tests, g_fail);
-
-	return g_fail != 0;
+	return report_summary();
 }
diff --git a/x86/vmx.c b/x86/vmx.c
index 4f0bb8d..2278078 100644
--- a/x86/vmx.c
+++ b/x86/vmx.c
@@ -37,7 +37,6 @@
 #include "smp.h"
 #include "io.h"
 
-int fails, tests;
 u32 *vmxon_region;
 struct vmcs *vmcs_root;
 u32 vpid_cnt;
@@ -63,17 +62,6 @@ extern void *vmx_return;
 extern void *entry_sysenter;
 extern void *guest_entry;
 
-void report(const char *name, int result)
-{
-	++tests;
-	if (result)
-		printf("PASS: %s\n", name);
-	else {
-		printf("FAIL: %s\n", name);
-		++fails;
-	}
-}
-
 static int make_vmcs_current(struct vmcs *vmcs)
 {
 	bool ret;
@@ -786,7 +774,6 @@ int main(void)
 
 	setup_vm();
 	setup_idt();
-	fails = tests = 0;
 	hypercall_field = 0;
 
 	if (!(cpuid(1).c & (1 << 5))) {
@@ -815,6 +802,5 @@ int main(void)
 			goto exit;
 
 exit:
-	printf("\nSUMMARY: %d tests, %d failures\n", tests, fails);
-	return fails ? 1 : 0;
+	return report_summary();
 }
diff --git a/x86/vmx.h b/x86/vmx.h
index 3867793..351c3d9 100644
--- a/x86/vmx.h
+++ b/x86/vmx.h
@@ -550,7 +550,6 @@ static inline void invept(unsigned long type, u64 eptp)
 	asm volatile("invept %0, %1\n" ::"m"(operand),"r"(type));
 }
 
-void report(const char *name, int result);
 void print_vmexit_info();
 void install_ept_entry(unsigned long *pml4, int pte_level,
 		unsigned long guest_addr, unsigned long pte,
