Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 963780df authored by Jon Medhurst's avatar Jon Medhurst
Browse files

ARM: kprobes: Add decoding table test coverage analysis



This is used to verify that all combinations of CPU instructions
described by the kprobes decoding tables have a test case.

Signed-off-by: default avatarJon Medhurst <tixy@yxit.co.uk>
Acked-by: default avatarNicolas Pitre <nicolas.pitre@linaro.org>
parent 68f360e7
Loading
Loading
Loading
Loading
+261 −0
Original line number Diff line number Diff line
@@ -178,6 +178,7 @@

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kprobes.h>

#include "kprobes.h"
@@ -528,6 +529,250 @@ static int table_test(const union decode_item *table)
}


/*
 * Decoding table test coverage analysis
 *
 * coverage_start() builds a coverage_table which contains a list of
 * coverage_entry's to match each entry in the specified kprobes instruction
 * decoding table.
 *
 * When test cases are run, coverage_add() is called to process each case.
 * This looks up the corresponding entry in the coverage_table and sets it as
 * being matched, as well as clearing the regs flag appropriate for the test.
 *
 * After all test cases have been run, coverage_end() is called to check that
 * all entries in coverage_table have been matched and that all regs flags are
 * cleared. I.e. that all possible combinations of instructions described by
 * the kprobes decoding tables have had a test case executed for them.
 */

bool coverage_fail;

#define MAX_COVERAGE_ENTRIES 256

struct coverage_entry {
	const struct decode_header	*header;
	unsigned			regs;
	unsigned			nesting;
	char				matched;
};

struct coverage_table {
	struct coverage_entry	*base;
	unsigned		num_entries;
	unsigned		nesting;
};

struct coverage_table coverage;

#define COVERAGE_ANY_REG	(1<<0)
#define COVERAGE_SP		(1<<1)
#define COVERAGE_PC		(1<<2)
#define COVERAGE_PCWB		(1<<3)

static const char coverage_register_lookup[16] = {
	[REG_TYPE_ANY]		= COVERAGE_ANY_REG | COVERAGE_SP | COVERAGE_PC,
	[REG_TYPE_SAMEAS16]	= COVERAGE_ANY_REG,
	[REG_TYPE_SP]		= COVERAGE_SP,
	[REG_TYPE_PC]		= COVERAGE_PC,
	[REG_TYPE_NOSP]		= COVERAGE_ANY_REG | COVERAGE_SP,
	[REG_TYPE_NOSPPC]	= COVERAGE_ANY_REG | COVERAGE_SP | COVERAGE_PC,
	[REG_TYPE_NOPC]		= COVERAGE_ANY_REG | COVERAGE_PC,
	[REG_TYPE_NOPCWB]	= COVERAGE_ANY_REG | COVERAGE_PC | COVERAGE_PCWB,
	[REG_TYPE_NOPCX]	= COVERAGE_ANY_REG,
	[REG_TYPE_NOSPPCX]	= COVERAGE_ANY_REG | COVERAGE_SP,
};

unsigned coverage_start_registers(const struct decode_header *h)
{
	unsigned regs = 0;
	int i;
	for (i = 0; i < 20; i += 4) {
		int r = (h->type_regs.bits >> (DECODE_TYPE_BITS + i)) & 0xf;
		regs |= coverage_register_lookup[r] << i;
	}
	return regs;
}

static int coverage_start_fn(const struct decode_header *h, void *args)
{
	struct coverage_table *coverage = (struct coverage_table *)args;
	enum decode_type type = h->type_regs.bits & DECODE_TYPE_MASK;
	struct coverage_entry *entry = coverage->base + coverage->num_entries;

	if (coverage->num_entries == MAX_COVERAGE_ENTRIES - 1) {
		pr_err("FAIL: Out of space for test coverage data");
		return -ENOMEM;
	}

	++coverage->num_entries;

	entry->header = h;
	entry->regs = coverage_start_registers(h);
	entry->nesting = coverage->nesting;
	entry->matched = false;

	if (type == DECODE_TYPE_TABLE) {
		struct decode_table *d = (struct decode_table *)h;
		int ret;
		++coverage->nesting;
		ret = table_iter(d->table.table, coverage_start_fn, coverage);
		--coverage->nesting;
		return ret;
	}

	return 0;
}

static int coverage_start(const union decode_item *table)
{
	coverage.base = kmalloc(MAX_COVERAGE_ENTRIES *
				sizeof(struct coverage_entry), GFP_KERNEL);
	coverage.num_entries = 0;
	coverage.nesting = 0;
	return table_iter(table, coverage_start_fn, &coverage);
}

static void
coverage_add_registers(struct coverage_entry *entry, kprobe_opcode_t insn)
{
	int regs = entry->header->type_regs.bits >> DECODE_TYPE_BITS;
	int i;
	for (i = 0; i < 20; i += 4) {
		enum decode_reg_type reg_type = (regs >> i) & 0xf;
		int reg = (insn >> i) & 0xf;
		int flag;

		if (!reg_type)
			continue;

		if (reg == 13)
			flag = COVERAGE_SP;
		else if (reg == 15)
			flag = COVERAGE_PC;
		else
			flag = COVERAGE_ANY_REG;
		entry->regs &= ~(flag << i);

		switch (reg_type) {

		case REG_TYPE_NONE:
		case REG_TYPE_ANY:
		case REG_TYPE_SAMEAS16:
			break;

		case REG_TYPE_SP:
			if (reg != 13)
				return;
			break;

		case REG_TYPE_PC:
			if (reg != 15)
				return;
			break;

		case REG_TYPE_NOSP:
			if (reg == 13)
				return;
			break;

		case REG_TYPE_NOSPPC:
		case REG_TYPE_NOSPPCX:
			if (reg == 13 || reg == 15)
				return;
			break;

		case REG_TYPE_NOPCWB:
			if (!is_writeback(insn))
				break;
			if (reg == 15) {
				entry->regs &= ~(COVERAGE_PCWB << i);
				return;
			}
			break;

		case REG_TYPE_NOPC:
		case REG_TYPE_NOPCX:
			if (reg == 15)
				return;
			break;
		}

	}
}

static void coverage_add(kprobe_opcode_t insn)
{
	struct coverage_entry *entry = coverage.base;
	struct coverage_entry *end = coverage.base + coverage.num_entries;
	bool matched = false;
	unsigned nesting = 0;

	for (; entry < end; ++entry) {
		const struct decode_header *h = entry->header;
		enum decode_type type = h->type_regs.bits & DECODE_TYPE_MASK;

		if (entry->nesting > nesting)
			continue; /* Skip sub-table we didn't match */

		if (entry->nesting < nesting)
			break; /* End of sub-table we were scanning */

		if (!matched) {
			if ((insn & h->mask.bits) != h->value.bits)
				continue;
			entry->matched = true;
		}

		switch (type) {

		case DECODE_TYPE_TABLE:
			++nesting;
			break;

		case DECODE_TYPE_CUSTOM:
		case DECODE_TYPE_SIMULATE:
		case DECODE_TYPE_EMULATE:
			coverage_add_registers(entry, insn);
			return;

		case DECODE_TYPE_OR:
			matched = true;
			break;

		case DECODE_TYPE_REJECT:
		default:
			return;
		}

	}
}

static void coverage_end(void)
{
	struct coverage_entry *entry = coverage.base;
	struct coverage_entry *end = coverage.base + coverage.num_entries;

	for (; entry < end; ++entry) {
		u32 mask = entry->header->mask.bits;
		u32 value = entry->header->value.bits;

		if (entry->regs) {
			pr_err("FAIL: Register test coverage missing for %08x %08x (%05x)\n",
				mask, value, entry->regs);
			coverage_fail = true;
		}
		if (!entry->matched) {
			pr_err("FAIL: Test coverage entry missing for %08x %08x\n",
				mask, value);
			coverage_fail = true;
		}
	}

	kfree(coverage.base);
}


/*
 * Framework for instruction set test cases
 */
@@ -1039,6 +1284,8 @@ static uintptr_t __used kprobes_test_case_start(const char *title, void *stack)
		}
	}

	coverage_add(current_instruction);

	if (end_arg->flags & ARG_FLAG_UNSUPPORTED) {
		if (register_test_probe(&test_case_probe) < 0)
			goto pass;
@@ -1213,8 +1460,13 @@ static int run_test_cases(void (*tests)(void), const union decode_item *table)
		return ret;

	pr_info("    Run test cases\n");
	ret = coverage_start(table);
	if (ret)
		return ret;

	tests();

	coverage_end();
	return 0;
}

@@ -1274,6 +1526,15 @@ static int __init run_all_tests(void)
		goto out;
	}

#if __LINUX_ARM_ARCH__ >= 7
	/* We are able to run all test cases so coverage should be complete */
	if (coverage_fail) {
		pr_err("FAIL: Test coverage checks failed\n");
		ret = -EINVAL;
		goto out;
	}
#endif

out:
	if (ret == 0)
		pr_info("Finished kprobe tests OK\n");