492 lines
15 KiB
C
492 lines
15 KiB
C
/*
|
|
* Copyright 2011-2017 by the PaX Team <pageexec@freemail.hu>
|
|
* Licensed under the GPL v2
|
|
*
|
|
* Note: the choice of the license means that the compilation process is
|
|
* NOT 'eligible' as defined by gcc's library exception to the GPL v3,
|
|
* but for the kernel it doesn't matter since it doesn't link against
|
|
* any of the gcc libraries
|
|
*
|
|
* gcc plugin to implement various sparse (source code checker) features
|
|
*
|
|
* TODO:
|
|
* - define separate __iomem, __percpu and __rcu address spaces (lots of code to patch)
|
|
*
|
|
* BUGS:
|
|
* - none known
|
|
*/
|
|
|
|
#include "gcc-common.h"
|
|
|
|
extern void c_register_addr_space (const char *str, addr_space_t as);
|
|
extern enum machine_mode default_addr_space_pointer_mode (addr_space_t);
|
|
extern enum machine_mode default_addr_space_address_mode (addr_space_t);
|
|
extern bool default_addr_space_valid_pointer_mode(enum machine_mode mode, addr_space_t as);
|
|
extern bool default_addr_space_legitimate_address_p(enum machine_mode mode, rtx mem, bool strict, addr_space_t as);
|
|
extern rtx default_addr_space_legitimize_address(rtx x, rtx oldx, enum machine_mode mode, addr_space_t as);
|
|
|
|
__visible int plugin_is_GPL_compatible;
|
|
|
|
static struct plugin_info checker_plugin_info = {
|
|
.version = "201602181345",
|
|
.help = "user\tturn on user/kernel address space checking\n"
|
|
"context\tturn on locking context checking\n"
|
|
};
|
|
|
|
#define ADDR_SPACE_KERNEL 0
|
|
#define ADDR_SPACE_FORCE_KERNEL 1
|
|
#define ADDR_SPACE_USER 2
|
|
#define ADDR_SPACE_FORCE_USER 3
|
|
#define ADDR_SPACE_IOMEM 0
|
|
#define ADDR_SPACE_FORCE_IOMEM 0
|
|
#define ADDR_SPACE_PERCPU 0
|
|
#define ADDR_SPACE_FORCE_PERCPU 0
|
|
#define ADDR_SPACE_RCU 0
|
|
#define ADDR_SPACE_FORCE_RCU 0
|
|
|
|
static enum machine_mode checker_addr_space_pointer_mode(addr_space_t addrspace)
|
|
{
|
|
return default_addr_space_pointer_mode(ADDR_SPACE_GENERIC);
|
|
}
|
|
|
|
static enum machine_mode checker_addr_space_address_mode(addr_space_t addrspace)
|
|
{
|
|
return default_addr_space_address_mode(ADDR_SPACE_GENERIC);
|
|
}
|
|
|
|
static bool checker_addr_space_valid_pointer_mode(enum machine_mode mode, addr_space_t as)
|
|
{
|
|
return default_addr_space_valid_pointer_mode(mode, as);
|
|
}
|
|
|
|
static bool checker_addr_space_legitimate_address_p(enum machine_mode mode, rtx mem, bool strict, addr_space_t as)
|
|
{
|
|
return default_addr_space_legitimate_address_p(mode, mem, strict, ADDR_SPACE_GENERIC);
|
|
}
|
|
|
|
static rtx checker_addr_space_legitimize_address(rtx x, rtx oldx, enum machine_mode mode, addr_space_t as)
|
|
{
|
|
return default_addr_space_legitimize_address(x, oldx, mode, as);
|
|
}
|
|
|
|
static bool checker_addr_space_subset_p(addr_space_t subset, addr_space_t superset)
|
|
{
|
|
if (subset == ADDR_SPACE_FORCE_KERNEL && superset == ADDR_SPACE_KERNEL)
|
|
return true;
|
|
|
|
if (subset == ADDR_SPACE_FORCE_USER && superset == ADDR_SPACE_USER)
|
|
return true;
|
|
|
|
if (subset == ADDR_SPACE_FORCE_IOMEM && superset == ADDR_SPACE_IOMEM)
|
|
return true;
|
|
|
|
if (subset == ADDR_SPACE_KERNEL && superset == ADDR_SPACE_FORCE_USER)
|
|
return true;
|
|
|
|
if (subset == ADDR_SPACE_KERNEL && superset == ADDR_SPACE_FORCE_IOMEM)
|
|
return true;
|
|
|
|
if (subset == ADDR_SPACE_USER && superset == ADDR_SPACE_FORCE_KERNEL)
|
|
return true;
|
|
|
|
if (subset == ADDR_SPACE_IOMEM && superset == ADDR_SPACE_FORCE_KERNEL)
|
|
return true;
|
|
|
|
return subset == superset;
|
|
}
|
|
|
|
static rtx checker_addr_space_convert(rtx op, tree from_type, tree to_type)
|
|
{
|
|
// addr_space_t from_as = TYPE_ADDR_SPACE(TREE_TYPE(from_type));
|
|
// addr_space_t to_as = TYPE_ADDR_SPACE(TREE_TYPE(to_type));
|
|
|
|
return op;
|
|
}
|
|
|
|
static void register_checker_address_spaces(void *event_data, void *data)
|
|
{
|
|
c_register_addr_space("__kernel", ADDR_SPACE_KERNEL);
|
|
c_register_addr_space("__force_kernel", ADDR_SPACE_FORCE_KERNEL);
|
|
c_register_addr_space("__user", ADDR_SPACE_USER);
|
|
c_register_addr_space("__force_user", ADDR_SPACE_FORCE_USER);
|
|
// c_register_addr_space("__iomem", ADDR_SPACE_IOMEM);
|
|
// c_register_addr_space("__force_iomem", ADDR_SPACE_FORCE_IOMEM);
|
|
// c_register_addr_space("__percpu", ADDR_SPACE_PERCPU);
|
|
// c_register_addr_space("__force_percpu", ADDR_SPACE_FORCE_PERCPU);
|
|
// c_register_addr_space("__rcu", ADDR_SPACE_RCU);
|
|
// c_register_addr_space("__force_rcu", ADDR_SPACE_FORCE_RCU);
|
|
|
|
targetm.addr_space.pointer_mode = checker_addr_space_pointer_mode;
|
|
targetm.addr_space.address_mode = checker_addr_space_address_mode;
|
|
targetm.addr_space.valid_pointer_mode = checker_addr_space_valid_pointer_mode;
|
|
targetm.addr_space.legitimate_address_p = checker_addr_space_legitimate_address_p;
|
|
// targetm.addr_space.legitimize_address = checker_addr_space_legitimize_address;
|
|
targetm.addr_space.subset_p = checker_addr_space_subset_p;
|
|
targetm.addr_space.convert = checker_addr_space_convert;
|
|
}
|
|
|
|
static bool split_context_attribute(tree args, tree *lock, tree *in, tree *out)
|
|
{
|
|
*in = TREE_VALUE(args);
|
|
|
|
if (TREE_CODE(*in) != INTEGER_CST) {
|
|
*lock = *in;
|
|
args = TREE_CHAIN(args);
|
|
*in = TREE_VALUE(args);
|
|
} else
|
|
*lock = NULL_TREE;
|
|
|
|
args = TREE_CHAIN(args);
|
|
if (*lock && !args)
|
|
return false;
|
|
|
|
*out = TREE_VALUE(args);
|
|
return true;
|
|
}
|
|
|
|
static tree handle_context_attribute(tree *node, tree name, tree args, int flags, bool *no_add_attrs)
|
|
{
|
|
*no_add_attrs = true;
|
|
tree lock, in, out;
|
|
|
|
if (TREE_CODE(*node) != FUNCTION_DECL) {
|
|
error("%qE attribute applies to functions only (%qD)", name, *node);
|
|
return NULL_TREE;
|
|
}
|
|
|
|
if (!split_context_attribute(args, &lock, &in, &out)) {
|
|
error("%qE attribute needs two integers after the lock expression", name);
|
|
return NULL_TREE;
|
|
}
|
|
|
|
if (TREE_CODE(in) != INTEGER_CST) {
|
|
error("the 'in' argument of the %qE attribute must be an integer (%qE)", name, in);
|
|
return NULL_TREE;
|
|
}
|
|
|
|
if (TREE_CODE(out) != INTEGER_CST) {
|
|
error("the 'out' argument of the %qE attribute must be an integer (%qE)", name, out);
|
|
return NULL_TREE;
|
|
}
|
|
|
|
*no_add_attrs = false;
|
|
return NULL_TREE;
|
|
}
|
|
|
|
static struct attribute_spec context_attr = {
|
|
.name = "context",
|
|
.min_length = 2,
|
|
.max_length = 3,
|
|
.decl_required = true,
|
|
.type_required = false,
|
|
.function_type_required = false,
|
|
.handler = handle_context_attribute,
|
|
#if BUILDING_GCC_VERSION >= 4007
|
|
.affects_type_identity = true
|
|
#endif
|
|
};
|
|
|
|
static void register_attributes(void *event_data, void *data)
|
|
{
|
|
register_attribute(&context_attr);
|
|
}
|
|
|
|
static const char context_function[] = "__context__";
|
|
static GTY(()) tree context_function_decl;
|
|
|
|
static const char context_error[] = "__context_error__";
|
|
static GTY(()) tree context_error_decl;
|
|
|
|
static void context_start_unit(void __unused *gcc_data, void __unused *user_data)
|
|
{
|
|
tree fntype, attr;
|
|
|
|
// void __context__(void *, int);
|
|
fntype = build_function_type_list(void_type_node, ptr_type_node, integer_type_node, NULL_TREE);
|
|
context_function_decl = build_fn_decl(context_function, fntype);
|
|
|
|
TREE_PUBLIC(context_function_decl) = 1;
|
|
TREE_USED(context_function_decl) = 1;
|
|
DECL_EXTERNAL(context_function_decl) = 1;
|
|
DECL_ARTIFICIAL(context_function_decl) = 1;
|
|
DECL_PRESERVE_P(context_function_decl) = 1;
|
|
// TREE_NOTHROW(context_function_decl) = 1;
|
|
// DECL_UNINLINABLE(context_function_decl) = 1;
|
|
DECL_ASSEMBLER_NAME(context_function_decl); // for LTO
|
|
lang_hooks.decls.pushdecl(context_function_decl);
|
|
|
|
// void __context_error__(const void *, int) __attribute__((error("context error")));
|
|
fntype = build_function_type_list(void_type_node, const_ptr_type_node, integer_type_node, NULL_TREE);
|
|
context_error_decl = build_fn_decl(context_error, fntype);
|
|
|
|
TREE_PUBLIC(context_error_decl) = 1;
|
|
TREE_USED(context_error_decl) = 1;
|
|
DECL_EXTERNAL(context_error_decl) = 1;
|
|
DECL_ARTIFICIAL(context_error_decl) = 1;
|
|
DECL_PRESERVE_P(context_error_decl) = 1;
|
|
// TREE_NOTHROW(context_error_decl) = 1;
|
|
// DECL_UNINLINABLE(context_error_decl) = 1;
|
|
TREE_THIS_VOLATILE(context_error_decl) = 1;
|
|
DECL_ASSEMBLER_NAME(context_error_decl);
|
|
|
|
attr = tree_cons(NULL, build_const_char_string(14, "context error"), NULL);
|
|
attr = tree_cons(get_identifier("error"), attr, NULL);
|
|
decl_attributes(&context_error_decl, attr, 0);
|
|
}
|
|
|
|
static bool context_gate(void)
|
|
{
|
|
tree context_attr;
|
|
|
|
return true;
|
|
|
|
context_attr = lookup_attribute("context", DECL_ATTRIBUTES(current_function_decl));
|
|
return context_attr != NULL_TREE;
|
|
}
|
|
|
|
static basic_block verify_context_before(gimple_stmt_iterator *gsi, tree context, tree inout, tree error)
|
|
{
|
|
gimple stmt;
|
|
basic_block cond_bb, join_bb, true_bb;
|
|
edge e;
|
|
location_t loc;
|
|
const char *file;
|
|
int line;
|
|
size_t len;
|
|
tree filename;
|
|
|
|
stmt = gsi_stmt(*gsi);
|
|
if (gimple_has_location(stmt)) {
|
|
loc = gimple_location(stmt);
|
|
file = gimple_filename(stmt);
|
|
line = gimple_lineno(stmt);
|
|
} else {
|
|
loc = DECL_SOURCE_LOCATION(current_function_decl);
|
|
file = DECL_SOURCE_FILE(current_function_decl);
|
|
line = DECL_SOURCE_LINE(current_function_decl);
|
|
}
|
|
gcc_assert(file);
|
|
|
|
// if (context != count) __context_error__(__FILE__, __LINE__);
|
|
stmt = gimple_build_cond(NE_EXPR, context, inout, NULL_TREE, NULL_TREE);
|
|
gimple_set_location(stmt, loc);
|
|
gsi_insert_before(gsi, stmt, GSI_NEW_STMT);
|
|
|
|
cond_bb = gsi_bb(*gsi);
|
|
gcc_assert(!gsi_end_p(*gsi));
|
|
gcc_assert(stmt == gsi_stmt(*gsi));
|
|
|
|
e = split_block(cond_bb, gsi_stmt(*gsi));
|
|
cond_bb = e->src;
|
|
join_bb = e->dest;
|
|
e->flags = EDGE_FALSE_VALUE;
|
|
e->probability = REG_BR_PROB_BASE;
|
|
|
|
true_bb = create_empty_bb(EXIT_BLOCK_PTR_FOR_FN(cfun)->prev_bb);
|
|
make_edge(cond_bb, true_bb, EDGE_TRUE_VALUE);
|
|
make_edge(true_bb, join_bb, EDGE_FALLTHRU);
|
|
|
|
set_immediate_dominator(CDI_DOMINATORS, true_bb, cond_bb);
|
|
set_immediate_dominator(CDI_DOMINATORS, join_bb, cond_bb);
|
|
|
|
gcc_assert(cond_bb->loop_father == join_bb->loop_father);
|
|
add_bb_to_loop(true_bb, cond_bb->loop_father);
|
|
|
|
// insert call to builtin_trap or __context_error__
|
|
*gsi = gsi_start_bb(true_bb);
|
|
|
|
// stmt = gimple_build_call(builtin_decl_implicit(BUILT_IN_TRAP), 0);
|
|
len = strlen(file) + 1;
|
|
filename = build_const_char_string(len, file);
|
|
filename = build1(ADDR_EXPR, const_ptr_type_node, filename);
|
|
stmt = gimple_build_call(error, 2, filename, build_int_cst(NULL_TREE, line));
|
|
gimple_set_location(stmt, loc);
|
|
gsi_insert_after(gsi, stmt, GSI_CONTINUE_LINKING);
|
|
|
|
*gsi = gsi_start_nondebug_bb(join_bb);
|
|
return join_bb;
|
|
}
|
|
|
|
static void update_context(gimple_stmt_iterator *gsi, tree context, int diff)
|
|
{
|
|
gimple assign;
|
|
tree op;
|
|
|
|
op = fold_build2_loc(UNKNOWN_LOCATION, PLUS_EXPR, integer_type_node, context, build_int_cst(integer_type_node, diff));
|
|
assign = gimple_build_assign(context, op);
|
|
gsi_insert_after(gsi, assign, GSI_NEW_STMT);
|
|
update_stmt(assign);
|
|
}
|
|
|
|
static basic_block track_context(basic_block bb, tree context)
|
|
{
|
|
gimple_stmt_iterator gsi;
|
|
gimple assign;
|
|
|
|
// adjust context according to the context information on any call stmt
|
|
for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
|
|
gimple stmt = gsi_stmt(gsi);
|
|
tree fndecl, context_attr;
|
|
tree lock, in, out;
|
|
int incount, outcount;
|
|
|
|
if (!is_gimple_call(stmt))
|
|
continue;
|
|
|
|
fndecl = gimple_call_fndecl(stmt);
|
|
if (!fndecl)
|
|
continue;
|
|
|
|
if (fndecl == context_function_decl) {
|
|
unsigned int num_ops = gimple_num_ops(stmt);
|
|
int diff = tree_to_shwi(gimple_op(stmt, num_ops - 1));
|
|
|
|
gcc_assert(diff);
|
|
update_context(&gsi, context, diff);
|
|
continue;
|
|
}
|
|
|
|
context_attr = lookup_attribute("context", DECL_ATTRIBUTES(fndecl));
|
|
if (!context_attr)
|
|
continue;
|
|
|
|
gcc_assert(split_context_attribute(TREE_VALUE(context_attr), &lock, &in, &out));
|
|
incount = tree_to_shwi(in);
|
|
outcount = tree_to_shwi(out);
|
|
bb = verify_context_before(&gsi, context, in, context_error_decl);
|
|
update_context(&gsi, context, outcount - incount);
|
|
}
|
|
|
|
return bb;
|
|
}
|
|
|
|
static bool bb_any_loop(basic_block bb)
|
|
{
|
|
return bb_loop_depth(bb) || (bb->flags & BB_IRREDUCIBLE_LOOP);
|
|
}
|
|
|
|
static unsigned int context_execute(void)
|
|
{
|
|
basic_block bb;
|
|
gimple assign;
|
|
gimple_stmt_iterator gsi;
|
|
tree context_attr, context;
|
|
tree lock, in, out;
|
|
|
|
loop_optimizer_init(LOOPS_NORMAL | LOOPS_HAVE_RECORDED_EXITS);
|
|
gcc_assert(current_loops);
|
|
|
|
calculate_dominance_info(CDI_DOMINATORS);
|
|
calculate_dominance_info(CDI_POST_DOMINATORS);
|
|
|
|
context_attr = lookup_attribute("context", DECL_ATTRIBUTES(current_function_decl));
|
|
if (context_attr) {
|
|
gcc_assert(split_context_attribute(TREE_VALUE(context_attr), &lock, &in, &out));
|
|
} else {
|
|
in = out = integer_zero_node;
|
|
}
|
|
|
|
// 1. create local context variable
|
|
context = create_tmp_var(integer_type_node, "context");
|
|
add_referenced_var(context);
|
|
mark_sym_for_renaming(context);
|
|
|
|
// 2. initialize local context variable
|
|
gcc_assert(single_succ_p(ENTRY_BLOCK_PTR_FOR_FN(cfun)));
|
|
bb = single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun));
|
|
if (!single_pred_p(bb)) {
|
|
gcc_assert(bb_any_loop(bb));
|
|
split_edge(single_succ_edge(ENTRY_BLOCK_PTR_FOR_FN(cfun)));
|
|
bb = single_succ(ENTRY_BLOCK_PTR_FOR_FN(cfun));
|
|
}
|
|
gsi = gsi_start_bb(bb);
|
|
assign = gimple_build_assign(context, in);
|
|
gsi_insert_before(&gsi, assign, GSI_NEW_STMT);
|
|
update_stmt(assign);
|
|
|
|
// 3. instrument each BB to track the local context variable
|
|
FOR_EACH_BB_FN(bb, cfun) {
|
|
bb = track_context(bb, context);
|
|
}
|
|
|
|
// 4. verify the local context variable against the expected state
|
|
if (EDGE_COUNT(EXIT_BLOCK_PTR_FOR_FN(cfun)->preds)) {
|
|
gcc_assert(single_pred_p(EXIT_BLOCK_PTR_FOR_FN(cfun)));
|
|
gsi = gsi_last_nondebug_bb(single_pred(EXIT_BLOCK_PTR_FOR_FN(cfun)));
|
|
verify_context_before(&gsi, context, out, context_error_decl);
|
|
}
|
|
|
|
free_dominance_info(CDI_DOMINATORS);
|
|
free_dominance_info(CDI_POST_DOMINATORS);
|
|
loop_optimizer_finalize();
|
|
return 0;
|
|
}
|
|
|
|
#define PASS_NAME context
|
|
#define PROPERTIES_REQUIRED PROP_gimple_leh | PROP_cfg
|
|
//#define TODO_FLAGS_START TODO_verify_ssa | TODO_verify_flow | TODO_verify_stmts
|
|
#define TODO_FLAGS_FINISH TODO_verify_ssa | TODO_verify_stmts | TODO_dump_func | TODO_verify_flow | TODO_update_ssa
|
|
#include "gcc-generate-gimple-pass.h"
|
|
|
|
__visible int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version)
|
|
{
|
|
const char * const plugin_name = plugin_info->base_name;
|
|
const int argc = plugin_info->argc;
|
|
const struct plugin_argument * const argv = plugin_info->argv;
|
|
int i;
|
|
bool enable_user, enable_context;
|
|
|
|
static const struct ggc_root_tab gt_ggc_r_gt_checker[] = {
|
|
{
|
|
.base = &context_function_decl,
|
|
.nelt = 1,
|
|
.stride = sizeof(context_function_decl),
|
|
.cb = >_ggc_mx_tree_node,
|
|
.pchw = >_pch_nx_tree_node
|
|
},
|
|
{
|
|
.base = &context_error_decl,
|
|
.nelt = 1,
|
|
.stride = sizeof(context_error_decl),
|
|
.cb = >_ggc_mx_tree_node,
|
|
.pchw = >_pch_nx_tree_node
|
|
},
|
|
LAST_GGC_ROOT_TAB
|
|
};
|
|
|
|
// PASS_INFO(context, "ssa", 1, PASS_POS_INSERT_AFTER);
|
|
PASS_INFO(context, "phiprop", 1, PASS_POS_INSERT_AFTER);
|
|
|
|
if (!plugin_default_version_check(version, &gcc_version)) {
|
|
error_gcc_version(version);
|
|
return 1;
|
|
}
|
|
|
|
register_callback(plugin_name, PLUGIN_INFO, NULL, &checker_plugin_info);
|
|
|
|
enable_user = false;
|
|
enable_context = false;
|
|
for (i = 0; i < argc; ++i) {
|
|
if (!strcmp(argv[i].key, "user")) {
|
|
enable_user = true;
|
|
continue;
|
|
}
|
|
if (!strcmp(argv[i].key, "context")) {
|
|
enable_context = true;
|
|
continue;
|
|
}
|
|
error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key);
|
|
}
|
|
|
|
if (enable_user)
|
|
register_callback(plugin_name, PLUGIN_PRAGMAS, register_checker_address_spaces, NULL);
|
|
if (enable_context) {
|
|
register_callback(plugin_name, PLUGIN_ATTRIBUTES, register_attributes, NULL);
|
|
register_callback(plugin_name, PLUGIN_START_UNIT, context_start_unit, NULL);
|
|
register_callback(plugin_name, PLUGIN_REGISTER_GGC_ROOTS, NULL, (void *)>_ggc_r_gt_checker);
|
|
register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &context_pass_info);
|
|
}
|
|
|
|
return 0;
|
|
}
|