Skip to content

Commit

Permalink
ARM: kprobes: Add decoding table test coverage analysis
Browse files Browse the repository at this point in the history
This is used to verify that all combinations of CPU instructions
described by the kprobes decoding tables have a test case.

Signed-off-by: Jon Medhurst <tixy@yxit.co.uk>
Acked-by: Nicolas Pitre <nicolas.pitre@linaro.org>
  • Loading branch information
Jon Medhurst committed Sep 20, 2011
1 parent 68f360e commit 963780d
Showing 1 changed file with 261 additions and 0 deletions.
261 changes: 261 additions & 0 deletions arch/arm/kernel/kprobes-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@

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

#include "kprobes.h"
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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");
Expand Down

0 comments on commit 963780d

Please sign in to comment.