@@ -410,6 +410,19 @@ config GCC_PLUGIN_LATENT_ENTROPY
* https://grsecurity.net/
* https://pax.grsecurity.net/
+config GCC_PLUGIN_CONSTIFY
+ bool "Make function pointer structures and marked variables read-only"
+ depends on GCC_PLUGINS && HAVE_ARCH_RARE_WRITE && !UML
+ help
+ By saying Y here the compiler will automatically constify a class
+ of types that contain only function pointers, as well as any
+ manually annotated structures. This reduces the kernel's attack
+ surface and also produces a better memory layout.
+
+ This plugin was ported from grsecurity/PaX. More information at:
+ * https://grsecurity.net/
+ * https://pax.grsecurity.net/
+
config HAVE_CC_STACKPROTECTOR
bool
help
@@ -189,6 +189,11 @@
#if GCC_VERSION >= 40500
+#ifdef CONSTIFY_PLUGIN
+#define __no_const __attribute__((no_const))
+#define __do_const __attribute__((do_const))
+#endif
+
#ifndef __CHECKER__
#ifdef LATENT_ENTROPY_PLUGIN
#define __latent_entropy __attribute__((latent_entropy))
@@ -471,6 +471,14 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s
# define __latent_entropy
#endif
+#ifndef __do_const
+# define __do_const
+#endif
+
+#ifndef __no_const
+# define __no_const
+#endif
+
/*
* Tell gcc if a function is cold. The compiler will assume any path
* directly leading to the call is unlikely.
@@ -5,6 +5,8 @@ ifdef CONFIG_GCC_PLUGINS
SANCOV_PLUGIN := -fplugin=$(objtree)/scripts/gcc-plugins/sancov_plugin.so
gcc-plugin-$(CONFIG_GCC_PLUGIN_CYC_COMPLEXITY) += cyc_complexity_plugin.so
+ gcc-plugin-$(CONFIG_GCC_PLUGIN_CONSTIFY) += constify_plugin.so
+ gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_CONSTIFY) += -DCONSTIFY_PLUGIN
gcc-plugin-$(CONFIG_GCC_PLUGIN_LATENT_ENTROPY) += latent_entropy_plugin.so
gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_LATENT_ENTROPY) += -DLATENT_ENTROPY_PLUGIN
new file mode 100644
@@ -0,0 +1,585 @@
+/*
+ * Copyright 2011 by Emese Revfy <re.emese@gmail.com>
+ * Copyright 2011-2016 by PaX Team <pageexec@freemail.hu>
+ * Licensed under the GPL v2, or (at your option) v3
+ *
+ * This gcc plugin constifies all structures which contain only function pointers or are explicitly marked for constification.
+ *
+ * Homepage:
+ * http://www.grsecurity.net/~ephox/const_plugin/
+ *
+ * Usage:
+ * $ gcc -I`gcc -print-file-name=plugin`/include -fPIC -shared -O2 -o constify_plugin.so constify_plugin.c
+ * $ gcc -fplugin=constify_plugin.so test.c -O2
+ */
+
+#include "gcc-common.h"
+
+// unused C type flag in all versions 4.5-6
+#define TYPE_CONSTIFY_VISITED(TYPE) TYPE_LANG_FLAG_4(TYPE)
+
+__visible int plugin_is_GPL_compatible;
+
+static bool enabled = true;
+
+static struct plugin_info const_plugin_info = {
+ .version = "201607241840vanilla",
+ .help = "disable\tturn off constification\n",
+};
+
+static struct {
+ const char *name;
+ const char *asm_op;
+} const_sections[] = {
+ {".init.rodata", "\t.section\t.init.rodata,\"a\""},
+ {".ref.rodata", "\t.section\t.ref.rodata,\"a\""},
+ {".devinit.rodata", "\t.section\t.devinit.rodata,\"a\""},
+ {".devexit.rodata", "\t.section\t.devexit.rodata,\"a\""},
+ {".cpuinit.rodata", "\t.section\t.cpuinit.rodata,\"a\""},
+ {".cpuexit.rodata", "\t.section\t.cpuexit.rodata,\"a\""},
+ {".meminit.rodata", "\t.section\t.meminit.rodata,\"a\""},
+ {".memexit.rodata", "\t.section\t.memexit.rodata,\"a\""},
+ {".data..read_only", "\t.section\t.data..read_only,\"a\""},
+};
+
+typedef struct {
+ bool has_fptr_field;
+ bool has_writable_field;
+ bool has_do_const_field;
+ bool has_no_const_field;
+} constify_info;
+
+static const_tree get_field_type(const_tree field)
+{
+ return strip_array_types(TREE_TYPE(field));
+}
+
+static bool is_fptr(const_tree field)
+{
+ /* XXX: disable automatic constification. */
+ return false;
+
+ const_tree ptr = get_field_type(field);
+
+ if (TREE_CODE(ptr) != POINTER_TYPE)
+ return false;
+
+ return TREE_CODE(TREE_TYPE(ptr)) == FUNCTION_TYPE;
+}
+
+/*
+ * determine whether the given structure type meets the requirements for automatic constification,
+ * including the constification attributes on nested structure types
+ */
+static void constifiable(const_tree node, constify_info *cinfo)
+{
+ const_tree field;
+
+ gcc_assert(TREE_CODE(node) == RECORD_TYPE || TREE_CODE(node) == UNION_TYPE);
+
+ // e.g., pointer to structure fields while still constructing the structure type
+ if (TYPE_FIELDS(node) == NULL_TREE)
+ return;
+
+ for (field = TYPE_FIELDS(node); field; field = TREE_CHAIN(field)) {
+ const_tree type = get_field_type(field);
+ enum tree_code code = TREE_CODE(type);
+
+ if (node == type)
+ continue;
+
+ if (is_fptr(field))
+ cinfo->has_fptr_field = true;
+ else if (code == RECORD_TYPE || code == UNION_TYPE) {
+ if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type)))
+ cinfo->has_do_const_field = true;
+ else if (lookup_attribute("no_const", TYPE_ATTRIBUTES(type)))
+ cinfo->has_no_const_field = true;
+ else
+ constifiable(type, cinfo);
+ } else if (!TREE_READONLY(field))
+ cinfo->has_writable_field = true;
+ }
+}
+
+static bool constified(const_tree node)
+{
+ constify_info cinfo = {
+ .has_fptr_field = false,
+ .has_writable_field = false,
+ .has_do_const_field = false,
+ .has_no_const_field = false
+ };
+
+ gcc_assert(TREE_CODE(node) == RECORD_TYPE || TREE_CODE(node) == UNION_TYPE);
+
+ if (lookup_attribute("no_const", TYPE_ATTRIBUTES(node))) {
+// gcc_assert(!TYPE_READONLY(node));
+ return false;
+ }
+
+ if (lookup_attribute("do_const", TYPE_ATTRIBUTES(node))) {
+ gcc_assert(TYPE_READONLY(node));
+ return true;
+ }
+
+ constifiable(node, &cinfo);
+ if ((!cinfo.has_fptr_field || cinfo.has_writable_field || cinfo.has_no_const_field) && !cinfo.has_do_const_field)
+ return false;
+
+ return TYPE_READONLY(node);
+}
+
+static void deconstify_tree(tree node);
+
+static void deconstify_type(tree type)
+{
+ tree field;
+
+ gcc_assert(TREE_CODE(type) == RECORD_TYPE || TREE_CODE(type) == UNION_TYPE);
+
+ for (field = TYPE_FIELDS(type); field; field = TREE_CHAIN(field)) {
+ const_tree fieldtype = get_field_type(field);
+
+ // special case handling of simple ptr-to-same-array-type members
+ if (TREE_CODE(TREE_TYPE(field)) == POINTER_TYPE) {
+ tree ptrtype = TREE_TYPE(TREE_TYPE(field));
+
+ if (TREE_TYPE(TREE_TYPE(field)) == type)
+ continue;
+ if (TREE_CODE(ptrtype) != RECORD_TYPE && TREE_CODE(ptrtype) != UNION_TYPE)
+ continue;
+ if (!constified(ptrtype))
+ continue;
+ if (TYPE_MAIN_VARIANT(ptrtype) == TYPE_MAIN_VARIANT(type))
+ TREE_TYPE(field) = build_pointer_type(build_qualified_type(type, TYPE_QUALS(ptrtype) & ~TYPE_QUAL_CONST));
+ continue;
+ }
+ if (TREE_CODE(fieldtype) != RECORD_TYPE && TREE_CODE(fieldtype) != UNION_TYPE)
+ continue;
+ if (!constified(fieldtype))
+ continue;
+
+ deconstify_tree(field);
+ TREE_READONLY(field) = 0;
+ }
+ TYPE_READONLY(type) = 0;
+ C_TYPE_FIELDS_READONLY(type) = 0;
+ if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) {
+ TYPE_ATTRIBUTES(type) = copy_list(TYPE_ATTRIBUTES(type));
+ TYPE_ATTRIBUTES(type) = remove_attribute("do_const", TYPE_ATTRIBUTES(type));
+ }
+}
+
+static void deconstify_tree(tree node)
+{
+ tree old_type, new_type, field;
+
+ old_type = TREE_TYPE(node);
+ while (TREE_CODE(old_type) == ARRAY_TYPE && TREE_CODE(TREE_TYPE(old_type)) != ARRAY_TYPE) {
+ node = TREE_TYPE(node) = copy_node(old_type);
+ old_type = TREE_TYPE(old_type);
+ }
+
+ gcc_assert(TREE_CODE(old_type) == RECORD_TYPE || TREE_CODE(old_type) == UNION_TYPE);
+ gcc_assert(TYPE_READONLY(old_type) && (TYPE_QUALS(old_type) & TYPE_QUAL_CONST));
+
+ new_type = build_qualified_type(old_type, TYPE_QUALS(old_type) & ~TYPE_QUAL_CONST);
+ TYPE_FIELDS(new_type) = copy_list(TYPE_FIELDS(new_type));
+ for (field = TYPE_FIELDS(new_type); field; field = TREE_CHAIN(field))
+ DECL_FIELD_CONTEXT(field) = new_type;
+
+ deconstify_type(new_type);
+
+ TREE_TYPE(node) = new_type;
+}
+
+static tree handle_no_const_attribute(tree *node, tree name, tree args, int flags, bool *no_add_attrs)
+{
+ tree type;
+ constify_info cinfo = {
+ .has_fptr_field = false,
+ .has_writable_field = false,
+ .has_do_const_field = false,
+ .has_no_const_field = false
+ };
+
+ *no_add_attrs = true;
+ if (TREE_CODE(*node) == FUNCTION_DECL) {
+ error("%qE attribute does not apply to functions (%qF)", name, *node);
+ return NULL_TREE;
+ }
+
+ if (TREE_CODE(*node) == PARM_DECL) {
+ error("%qE attribute does not apply to function parameters (%qD)", name, *node);
+ return NULL_TREE;
+ }
+
+ if (TREE_CODE(*node) == VAR_DECL) {
+ error("%qE attribute does not apply to variables (%qD)", name, *node);
+ return NULL_TREE;
+ }
+
+ if (TYPE_P(*node)) {
+ type = *node;
+ } else {
+ if (TREE_CODE(*node) != TYPE_DECL) {
+ error("%qE attribute does not apply to %qD (%qT)", name, *node, TREE_TYPE(*node));
+ return NULL_TREE;
+ }
+ type = TREE_TYPE(*node);
+ }
+
+ if (TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE) {
+ error("%qE attribute used on %qT applies to struct and union types only", name, type);
+ return NULL_TREE;
+ }
+
+ if (lookup_attribute(IDENTIFIER_POINTER(name), TYPE_ATTRIBUTES(type))) {
+ error("%qE attribute is already applied to the type %qT", name, type);
+ return NULL_TREE;
+ }
+
+ if (TYPE_P(*node)) {
+ if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type)))
+ error("%qE attribute used on type %qT is incompatible with 'do_const'", name, type);
+ else
+ *no_add_attrs = false;
+ return NULL_TREE;
+ }
+
+ constifiable(type, &cinfo);
+ if ((cinfo.has_fptr_field && !cinfo.has_writable_field && !cinfo.has_no_const_field) || lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) {
+ if (enabled) {
+ if TYPE_P(*node)
+ deconstify_type(*node);
+ else
+ deconstify_tree(*node);
+ }
+ if (TYPE_P(*node))
+ TYPE_CONSTIFY_VISITED(*node) = 1;
+ else
+ TYPE_CONSTIFY_VISITED(TREE_TYPE(*node)) = 1;
+ return NULL_TREE;
+ }
+
+ if (enabled && TYPE_FIELDS(type))
+ error("%qE attribute used on type %qT that is not constified", name, type);
+ return NULL_TREE;
+}
+
+static void constify_type(tree type)
+{
+ gcc_assert(type == TYPE_MAIN_VARIANT(type));
+ TYPE_READONLY(type) = 1;
+ C_TYPE_FIELDS_READONLY(type) = 1;
+ TYPE_CONSTIFY_VISITED(type) = 1;
+// TYPE_ATTRIBUTES(type) = copy_list(TYPE_ATTRIBUTES(type));
+// TYPE_ATTRIBUTES(type) = tree_cons(get_identifier("do_const"), NULL_TREE, TYPE_ATTRIBUTES(type));
+}
+
+static tree handle_do_const_attribute(tree *node, tree name, tree args, int flags, bool *no_add_attrs)
+{
+ *no_add_attrs = true;
+ if (!TYPE_P(*node)) {
+ error("%qE attribute applies to types only (%qD)", name, *node);
+ return NULL_TREE;
+ }
+
+ if (TREE_CODE(*node) != RECORD_TYPE && TREE_CODE(*node) != UNION_TYPE) {
+ error("%qE attribute used on %qT applies to struct and union types only", name, *node);
+ return NULL_TREE;
+ }
+
+ if (lookup_attribute(IDENTIFIER_POINTER(name), TYPE_ATTRIBUTES(*node))) {
+ error("%qE attribute used on %qT is already applied to the type", name, *node);
+ return NULL_TREE;
+ }
+
+ if (lookup_attribute("no_const", TYPE_ATTRIBUTES(*node))) {
+ error("%qE attribute used on %qT is incompatible with 'no_const'", name, *node);
+ return NULL_TREE;
+ }
+
+ *no_add_attrs = false;
+ return NULL_TREE;
+}
+
+static struct attribute_spec no_const_attr = {
+ .name = "no_const",
+ .min_length = 0,
+ .max_length = 0,
+ .decl_required = false,
+ .type_required = false,
+ .function_type_required = false,
+ .handler = handle_no_const_attribute,
+#if BUILDING_GCC_VERSION >= 4007
+ .affects_type_identity = true
+#endif
+};
+
+static struct attribute_spec do_const_attr = {
+ .name = "do_const",
+ .min_length = 0,
+ .max_length = 0,
+ .decl_required = false,
+ .type_required = false,
+ .function_type_required = false,
+ .handler = handle_do_const_attribute,
+#if BUILDING_GCC_VERSION >= 4007
+ .affects_type_identity = true
+#endif
+};
+
+static void register_attributes(void *event_data, void *data)
+{
+ register_attribute(&no_const_attr);
+ register_attribute(&do_const_attr);
+}
+
+static void finish_type(void *event_data, void *data)
+{
+ tree type = (tree)event_data;
+ constify_info cinfo = {
+ .has_fptr_field = false,
+ .has_writable_field = false,
+ .has_do_const_field = false,
+ .has_no_const_field = false
+ };
+
+ if (type == NULL_TREE || type == error_mark_node)
+ return;
+
+#if BUILDING_GCC_VERSION >= 5000
+ if (TREE_CODE(type) == ENUMERAL_TYPE)
+ return;
+#endif
+
+ if (TYPE_FIELDS(type) == NULL_TREE || TYPE_CONSTIFY_VISITED(type))
+ return;
+
+ constifiable(type, &cinfo);
+
+ if (lookup_attribute("no_const", TYPE_ATTRIBUTES(type))) {
+ if ((cinfo.has_fptr_field && !cinfo.has_writable_field && !cinfo.has_no_const_field) || cinfo.has_do_const_field) {
+ deconstify_type(type);
+ TYPE_CONSTIFY_VISITED(type) = 1;
+ } else
+ error("'no_const' attribute used on type %qT that is not constified", type);
+ return;
+ }
+
+ if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) {
+ if (!cinfo.has_writable_field && !cinfo.has_no_const_field) {
+ error("'do_const' attribute used on type %qT that is%sconstified", type, cinfo.has_fptr_field ? " " : " not ");
+ return;
+ }
+ constify_type(type);
+ return;
+ }
+
+ if (cinfo.has_fptr_field && !cinfo.has_writable_field && !cinfo.has_no_const_field) {
+ if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) {
+ error("'do_const' attribute used on type %qT that is constified", type);
+ return;
+ }
+ constify_type(type);
+ return;
+ }
+
+ deconstify_type(type);
+ TYPE_CONSTIFY_VISITED(type) = 1;
+}
+
+static bool is_constified_var(varpool_node_ptr node)
+{
+ tree var = NODE_DECL(node);
+ tree type = TREE_TYPE(var);
+
+ if (node->alias)
+ return false;
+
+ if (DECL_EXTERNAL(var))
+ return false;
+
+ // XXX handle more complex nesting of arrays/structs
+ if (TREE_CODE(type) == ARRAY_TYPE)
+ type = TREE_TYPE(type);
+
+ if (TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE)
+ return false;
+
+ if (!TYPE_READONLY(type) || !C_TYPE_FIELDS_READONLY(type))
+ return false;
+
+ if (!TYPE_CONSTIFY_VISITED(type))
+ return false;
+
+ return true;
+}
+
+static void check_section_mismatch(varpool_node_ptr node)
+{
+ tree var, section;
+ size_t i;
+ const char *name;
+
+ var = NODE_DECL(node);
+ name = get_decl_section_name(var);
+ section = lookup_attribute("section", DECL_ATTRIBUTES(var));
+ if (!section) {
+ if (name) {
+ fprintf(stderr, "DECL_SECTION [%s] ", name);
+ dump_varpool_node(stderr, node);
+ gcc_unreachable();
+ }
+ return;
+ } else
+ gcc_assert(name);
+
+//fprintf(stderr, "SECTIONAME: [%s] ", get_decl_section_name(var));
+//debug_tree(var);
+
+ gcc_assert(!TREE_CHAIN(section));
+ gcc_assert(TREE_VALUE(section));
+
+ section = TREE_VALUE(TREE_VALUE(section));
+ gcc_assert(!strcmp(TREE_STRING_POINTER(section), name));
+//debug_tree(section);
+
+ for (i = 0; i < ARRAY_SIZE(const_sections); i++)
+ if (!strcmp(const_sections[i].name, name))
+ return;
+
+ error_at(DECL_SOURCE_LOCATION(var), "constified variable %qD placed into writable section %E", var, section);
+}
+
+// this works around a gcc bug/feature where uninitialized globals
+// are moved into the .bss section regardless of any constification
+// see gcc/varasm.c:bss_initializer_p()
+static void fix_initializer(varpool_node_ptr node)
+{
+ tree var = NODE_DECL(node);
+ tree type = TREE_TYPE(var);
+
+ if (DECL_INITIAL(var))
+ return;
+
+ DECL_INITIAL(var) = build_constructor(type, NULL);
+// inform(DECL_SOURCE_LOCATION(var), "constified variable %qE moved into .rodata", var);
+}
+
+static void check_global_variables(void *event_data, void *data)
+{
+ varpool_node_ptr node;
+
+ FOR_EACH_VARIABLE(node) {
+ if (!is_constified_var(node))
+ continue;
+
+ check_section_mismatch(node);
+ fix_initializer(node);
+ }
+}
+
+static unsigned int check_local_variables_execute(void)
+{
+ unsigned int ret = 0;
+ tree var;
+
+ unsigned int i;
+
+ FOR_EACH_LOCAL_DECL(cfun, i, var) {
+ tree type = TREE_TYPE(var);
+
+ gcc_assert(DECL_P(var));
+ if (is_global_var(var))
+ continue;
+
+ if (TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE)
+ continue;
+
+ if (!TYPE_READONLY(type) || !C_TYPE_FIELDS_READONLY(type))
+ continue;
+
+ if (!TYPE_CONSTIFY_VISITED(type))
+ continue;
+
+ error_at(DECL_SOURCE_LOCATION(var), "constified variable %qE cannot be local", var);
+ ret = 1;
+ }
+ return ret;
+}
+
+#define PASS_NAME check_local_variables
+#define NO_GATE
+#include "gcc-generate-gimple-pass.h"
+
+static unsigned int (*old_section_type_flags)(tree decl, const char *name, int reloc);
+
+static unsigned int constify_section_type_flags(tree decl, const char *name, int reloc)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(const_sections); i++)
+ if (!strcmp(const_sections[i].name, name))
+ return 0;
+
+ return old_section_type_flags(decl, name, reloc);
+}
+
+static void constify_start_unit(void *gcc_data, void *user_data)
+{
+// size_t i;
+
+// for (i = 0; i < ARRAY_SIZE(const_sections); i++)
+// const_sections[i].section = get_unnamed_section(0, output_section_asm_op, const_sections[i].asm_op);
+// const_sections[i].section = get_section(const_sections[i].name, 0, NULL);
+
+ old_section_type_flags = targetm.section_type_flags;
+ targetm.section_type_flags = constify_section_type_flags;
+}
+
+__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;
+
+ struct register_pass_info check_local_variables_pass_info;
+
+ check_local_variables_pass_info.pass = make_check_local_variables_pass();
+ check_local_variables_pass_info.reference_pass_name = "ssa";
+ check_local_variables_pass_info.ref_pass_instance_number = 1;
+ check_local_variables_pass_info.pos_op = PASS_POS_INSERT_BEFORE;
+
+ if (!plugin_default_version_check(version, &gcc_version)) {
+ error(G_("incompatible gcc/plugin versions"));
+ return 1;
+ }
+
+ for (i = 0; i < argc; ++i) {
+ if (!(strcmp(argv[i].key, "disable"))) {
+ enabled = false;
+ continue;
+ }
+ error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key);
+ }
+
+ if (strncmp(lang_hooks.name, "GNU C", 5) && !strncmp(lang_hooks.name, "GNU C+", 6)) {
+ inform(UNKNOWN_LOCATION, G_("%s supports C only, not %s"), plugin_name, lang_hooks.name);
+ enabled = false;
+ }
+
+ register_callback(plugin_name, PLUGIN_INFO, NULL, &const_plugin_info);
+ if (enabled) {
+ register_callback(plugin_name, PLUGIN_ALL_IPA_PASSES_START, check_global_variables, NULL);
+ register_callback(plugin_name, PLUGIN_FINISH_TYPE, finish_type, NULL);
+ register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &check_local_variables_pass_info);
+ register_callback(plugin_name, PLUGIN_START_UNIT, constify_start_unit, NULL);
+ }
+ register_callback(plugin_name, PLUGIN_ATTRIBUTES, register_attributes, NULL);
+
+ return 0;
+}
This is a port of the PaX/grsecurity constify plugin. However, it has the automatic function-pointer struct detection temporarily disabled. As a result, this will only recognize the __do_const annotation, which makes all instances of a marked structure read-only. The rare_write() infrastructure can be used to make changes to such variables. Signed-off-by: Kees Cook <keescook@chromium.org> --- arch/Kconfig | 13 + include/linux/compiler-gcc.h | 5 + include/linux/compiler.h | 8 + scripts/Makefile.gcc-plugins | 2 + scripts/gcc-plugins/constify_plugin.c | 585 ++++++++++++++++++++++++++++++++++ 5 files changed, 613 insertions(+) create mode 100644 scripts/gcc-plugins/constify_plugin.c