mirror of
https://github.com/FEX-Emu/linux.git
synced 2025-01-08 02:21:18 +00:00
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: Jon Medhurst <tixy@yxit.co.uk> Acked-by: Nicolas Pitre <nicolas.pitre@linaro.org>
This commit is contained in:
parent
68f360e753
commit
963780dfe3
@ -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");
|
||||
|
Loading…
Reference in New Issue
Block a user