diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 184a6294bf1..4d2bb892ad7 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -1,3 +1,28 @@ +2017-10-20 Igor Tsimbalist + + * c-attribs.c (handle_nocf_check_attribute): New function. + (c_common_attribute_table): Add 'nocf_check' handling. + * gimple-parser.c: Add second argument NULL to + gimple_build_call_from_tree. + * attrib.c (comp_type_attributes): Check nocf_check attribute. + * cfgexpand.c (expand_call_stmt): Set REG_CALL_NOCF_CHECK for + call insn. + * combine.c (distribute_notes): Add REG_CALL_NOCF_CHECK handling. + * common.opt: Add fcf-protection flag. + * emit-rtl.c (try_split): Add REG_CALL_NOCF_CHECK handling. + * flag-types.h: Add enum cf_protection_level. + * gimple.c (gimple_build_call_from_tree): Add second parameter. + Add 'nocf_check' attribute propagation to gimple call. + * gimple.h (gf_mask): Add GF_CALL_NOCF_CHECK. + (gimple_build_call_from_tree): Update prototype. + (gimple_call_nocf_check_p): New function. + (gimple_call_set_nocf_check): Likewise. + * gimplify.c: Add second argument to gimple_build_call_from_tree. + * ipa-icf.c: Add nocf_check attribute in statement hash. + * recog.c (peep2_attempt): Add REG_CALL_NOCF_CHECK handling. + * reg-notes.def: Add REG_NOTE (CALL_NOCF_CHECK). + * toplev.c (process_options): Add flag_cf_protection handling. + 2017-10-19 Jan Hubicka * x86-tune-costs.h (core_cost): Fix div, move and sqrt latencies. diff --git a/gcc/attribs.c b/gcc/attribs.c index 4ef35b861f8..ed76a8dab6f 100644 --- a/gcc/attribs.c +++ b/gcc/attribs.c @@ -1182,6 +1182,9 @@ comp_type_attributes (const_tree type1, const_tree type2) } if (lookup_attribute ("transaction_safe", CONST_CAST_TREE (a))) return 0; + if ((lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type1)) != NULL) + ^ (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (type2)) != NULL)) + return 0; /* As some type combinations - like default calling-convention - might be compatible, we have to call the target hook to get the final result. */ return targetm.comp_type_attributes (type1, type2); diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c index bd8ca306c2d..bb75cba4c39 100644 --- a/gcc/c-family/c-attribs.c +++ b/gcc/c-family/c-attribs.c @@ -65,6 +65,7 @@ static tree handle_asan_odr_indicator_attribute (tree *, tree, tree, int, static tree handle_stack_protect_attribute (tree *, tree, tree, int, bool *); static tree handle_noinline_attribute (tree *, tree, tree, int, bool *); static tree handle_noclone_attribute (tree *, tree, tree, int, bool *); +static tree handle_nocf_check_attribute (tree *, tree, tree, int, bool *); static tree handle_noicf_attribute (tree *, tree, tree, int, bool *); static tree handle_noipa_attribute (tree *, tree, tree, int, bool *); static tree handle_leaf_attribute (tree *, tree, tree, int, bool *); @@ -367,6 +368,8 @@ const struct attribute_spec c_common_attribute_table[] = { "patchable_function_entry", 1, 2, true, false, false, handle_patchable_function_entry_attribute, false }, + { "nocf_check", 0, 0, false, true, true, + handle_nocf_check_attribute, true }, { NULL, 0, 0, false, false, false, NULL, false } }; @@ -772,6 +775,30 @@ handle_noclone_attribute (tree *node, tree name, return NULL_TREE; } +/* Handle a "nocf_check" attribute; arguments as in + struct attribute_spec.handler. */ + +static tree +handle_nocf_check_attribute (tree *node, tree name, + tree ARG_UNUSED (args), + int ARG_UNUSED (flags), bool *no_add_attrs) +{ + if (TREE_CODE (*node) != FUNCTION_TYPE + && TREE_CODE (*node) != METHOD_TYPE) + { + warning (OPT_Wattributes, "%qE attribute ignored", name); + *no_add_attrs = true; + } + else if (!(flag_cf_protection & CF_BRANCH)) + { + warning (OPT_Wattributes, "%qE attribute ignored. Use " + "-fcf-protection option to enable it", name); + *no_add_attrs = true; + } + + return NULL_TREE; +} + /* Handle a "no_icf" attribute; arguments as in struct attribute_spec.handler. */ diff --git a/gcc/c/gimple-parser.c b/gcc/c/gimple-parser.c index 22f58f4e820..c2e31df218c 100644 --- a/gcc/c/gimple-parser.c +++ b/gcc/c/gimple-parser.c @@ -276,7 +276,7 @@ c_parser_gimple_statement (c_parser *parser, gimple_seq *seq) && TREE_CODE (lhs.value) == CALL_EXPR) { gimple *call; - call = gimple_build_call_from_tree (lhs.value); + call = gimple_build_call_from_tree (lhs.value, NULL); gimple_seq_add_stmt (seq, call); gimple_set_location (call, loc); return; @@ -407,7 +407,7 @@ c_parser_gimple_statement (c_parser *parser, gimple_seq *seq) rhs = c_parser_gimple_unary_expression (parser); if (rhs.value != error_mark_node) { - gimple *call = gimple_build_call_from_tree (rhs.value); + gimple *call = gimple_build_call_from_tree (rhs.value, NULL); gimple_call_set_lhs (call, lhs.value); gimple_seq_add_stmt (seq, call); gimple_set_location (call, loc); diff --git a/gcc/cfgexpand.c b/gcc/cfgexpand.c index a255ac3c6f0..d0e07821489 100644 --- a/gcc/cfgexpand.c +++ b/gcc/cfgexpand.c @@ -2658,12 +2658,28 @@ expand_call_stmt (gcall *stmt) } } + rtx_insn *before_call = get_last_insn (); lhs = gimple_call_lhs (stmt); if (lhs) expand_assignment (lhs, exp, false); else expand_expr (exp, const0_rtx, VOIDmode, EXPAND_NORMAL); + /* If the gimple call is an indirect call and has 'nocf_check' + attribute find a generated CALL insn to mark it as no + control-flow verification is needed. */ + if (gimple_call_nocf_check_p (stmt) + && !gimple_call_fndecl (stmt)) + { + rtx_insn *last = get_last_insn (); + while (!CALL_P (last) + && last != before_call) + last = PREV_INSN (last); + + if (last != before_call) + add_reg_note (last, REG_CALL_NOCF_CHECK, const0_rtx); + } + mark_transaction_restart_calls (stmt); } diff --git a/gcc/combine.c b/gcc/combine.c index 757ae6fc93e..a58bb1e9470 100644 --- a/gcc/combine.c +++ b/gcc/combine.c @@ -14185,6 +14185,7 @@ distribute_notes (rtx notes, rtx_insn *from_insn, rtx_insn *i3, rtx_insn *i2, case REG_SETJMP: case REG_TM: case REG_CALL_DECL: + case REG_CALL_NOCF_CHECK: /* These notes must remain with the call. It should not be possible for both I2 and I3 to be a call. */ if (CALL_P (i3)) diff --git a/gcc/common.opt b/gcc/common.opt index c95da640174..8d62ec5044d 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -1620,6 +1620,29 @@ finline-atomics Common Report Var(flag_inline_atomics) Init(1) Optimization Inline __atomic operations when a lock free instruction sequence is available. +fcf-protection +Common RejectNegative Alias(fcf-protection=,full) + +fcf-protection= +Common Report Joined RejectNegative Enum(cf_protection_level) Var(flag_cf_protection) Init(CF_NONE) +-fcf-protection=[full|branch|return|none] Instrument functions with checks to verify jump/call/return control-flow transfer +instructions have valid targets. + +Enum +Name(cf_protection_level) Type(enum cf_protection_level) UnknownError(unknown Cotrol-Flow Protection Level %qs) + +EnumValue +Enum(cf_protection_level) String(full) Value(CF_FULL) + +EnumValue +Enum(cf_protection_level) String(branch) Value(CF_BRANCH) + +EnumValue +Enum(cf_protection_level) String(return) Value(CF_RETURN) + +EnumValue +Enum(cf_protection_level) String(none) Value(CF_NONE) + finstrument-functions Common Report Var(flag_instrument_function_entry_exit) Instrument function entry and exit with profiling calls. diff --git a/gcc/emit-rtl.c b/gcc/emit-rtl.c index e790cbcb6ff..d91988e902d 100644 --- a/gcc/emit-rtl.c +++ b/gcc/emit-rtl.c @@ -3789,6 +3789,7 @@ try_split (rtx pat, rtx_insn *trial, int last) case REG_NORETURN: case REG_SETJMP: case REG_TM: + case REG_CALL_NOCF_CHECK: for (insn = insn_last; insn != NULL_RTX; insn = PREV_INSN (insn)) { if (CALL_P (insn)) diff --git a/gcc/flag-types.h b/gcc/flag-types.h index bfea17408cb..2b2302963f0 100644 --- a/gcc/flag-types.h +++ b/gcc/flag-types.h @@ -326,4 +326,13 @@ enum gfc_convert }; +/* Control-Flow Protection values. */ +enum cf_protection_level +{ + CF_NONE = 0, + CF_BRANCH = 1 << 0, + CF_RETURN = 1 << 1, + CF_FULL = CF_BRANCH | CF_RETURN, + CF_SET = 1 << 2 +}; #endif /* ! GCC_FLAG_TYPES_H */ diff --git a/gcc/gimple.c b/gcc/gimple.c index aaf1cc2060f..1f291e10286 100644 --- a/gcc/gimple.c +++ b/gcc/gimple.c @@ -346,7 +346,7 @@ gimple_build_call_internal_vec (enum internal_fn fn, vec args) this fact. */ gcall * -gimple_build_call_from_tree (tree t) +gimple_build_call_from_tree (tree t, tree fnptrtype) { unsigned i, nargs; gcall *call; @@ -379,6 +379,23 @@ gimple_build_call_from_tree (tree t) gimple_set_no_warning (call, TREE_NO_WARNING (t)); gimple_call_set_with_bounds (call, CALL_WITH_BOUNDS_P (t)); + if (fnptrtype) + { + gimple_call_set_fntype (call, TREE_TYPE (fnptrtype)); + + /* Check if it's an indirect CALL and the type has the + nocf_check attribute. In that case propagate the information + to the gimple CALL insn. */ + if (!fndecl) + { + gcc_assert (POINTER_TYPE_P (fnptrtype)); + tree fntype = TREE_TYPE (fnptrtype); + + if (lookup_attribute ("nocf_check", TYPE_ATTRIBUTES (fntype))) + gimple_call_set_nocf_check (call, TRUE); + } + } + return call; } diff --git a/gcc/gimple.h b/gcc/gimple.h index 6213c49b91f..334def89398 100644 --- a/gcc/gimple.h +++ b/gcc/gimple.h @@ -148,6 +148,7 @@ enum gf_mask { GF_CALL_WITH_BOUNDS = 1 << 8, GF_CALL_MUST_TAIL_CALL = 1 << 9, GF_CALL_BY_DESCRIPTOR = 1 << 10, + GF_CALL_NOCF_CHECK = 1 << 11, GF_OMP_PARALLEL_COMBINED = 1 << 0, GF_OMP_PARALLEL_GRID_PHONY = 1 << 1, GF_OMP_TASK_TASKLOOP = 1 << 0, @@ -1425,7 +1426,7 @@ gcall *gimple_build_call (tree, unsigned, ...); gcall *gimple_build_call_valist (tree, unsigned, va_list); gcall *gimple_build_call_internal (enum internal_fn, unsigned, ...); gcall *gimple_build_call_internal_vec (enum internal_fn, vec ); -gcall *gimple_build_call_from_tree (tree); +gcall *gimple_build_call_from_tree (tree, tree); gassign *gimple_build_assign (tree, tree CXX_MEM_STAT_INFO); gassign *gimple_build_assign (tree, enum tree_code, tree, tree, tree CXX_MEM_STAT_INFO); @@ -2893,6 +2894,25 @@ gimple_call_set_with_bounds (gimple *gs, bool with_bounds) } +/* Return true if call GS is marked as nocf_check. */ + +static inline bool +gimple_call_nocf_check_p (const gcall *gs) +{ + return (gs->subcode & GF_CALL_NOCF_CHECK) != 0; +} + +/* Mark statement GS as nocf_check call. */ + +static inline void +gimple_call_set_nocf_check (gcall *gs, bool nocf_check) +{ + if (nocf_check) + gs->subcode |= GF_CALL_NOCF_CHECK; + else + gs->subcode &= ~GF_CALL_NOCF_CHECK; +} + /* Return the target of internal call GS. */ static inline enum internal_fn diff --git a/gcc/gimplify.c b/gcc/gimplify.c index 2c1ec852210..4c245103fc4 100644 --- a/gcc/gimplify.c +++ b/gcc/gimplify.c @@ -3378,8 +3378,7 @@ gimplify_call_expr (tree *expr_p, gimple_seq *pre_p, bool want_value) /* The CALL_EXPR in *EXPR_P is already in GIMPLE form, so all we have to do is replicate it as a GIMPLE_CALL tuple. */ gimple_stmt_iterator gsi; - call = gimple_build_call_from_tree (*expr_p); - gimple_call_set_fntype (call, TREE_TYPE (fnptrtype)); + call = gimple_build_call_from_tree (*expr_p, fnptrtype); notice_special_calls (call); if (EXPR_CILK_SPAWN (*expr_p)) gimplify_cilk_detach (pre_p); @@ -5660,8 +5659,7 @@ gimplify_modify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p, CALL_EXPR_ARG (*from_p, 2)); else { - call_stmt = gimple_build_call_from_tree (*from_p); - gimple_call_set_fntype (call_stmt, TREE_TYPE (fnptrtype)); + call_stmt = gimple_build_call_from_tree (*from_p, fnptrtype); } } notice_special_calls (call_stmt); diff --git a/gcc/ipa-icf.c b/gcc/ipa-icf.c index 4d152ceab1e..e666d5ab2d0 100644 --- a/gcc/ipa-icf.c +++ b/gcc/ipa-icf.c @@ -1422,6 +1422,7 @@ sem_function::init (void) } } + hstate.commit_flag (); gcode_hash = hstate.end (); bb_sizes.safe_push (nondbg_stmt_count); @@ -1644,6 +1645,11 @@ sem_function::hash_stmt (gimple *stmt, inchash::hash &hstate) if (gimple_op (stmt, i)) add_type (TREE_TYPE (gimple_op (stmt, i)), hstate); } + /* Consider nocf_check attribute in hash as it affects code + generation. */ + if (code == GIMPLE_CALL + && flag_cf_protection & CF_BRANCH) + hstate.add_flag (gimple_call_nocf_check_p (as_a (stmt))); default: break; } diff --git a/gcc/recog.c b/gcc/recog.c index b8e9b1ba3a8..9aaa6cd7a73 100644 --- a/gcc/recog.c +++ b/gcc/recog.c @@ -3381,6 +3381,7 @@ peep2_attempt (basic_block bb, rtx_insn *insn, int match_len, rtx_insn *attempt) case REG_NORETURN: case REG_SETJMP: case REG_TM: + case REG_CALL_NOCF_CHECK: add_reg_note (new_insn, REG_NOTE_KIND (note), XEXP (note, 0)); break; diff --git a/gcc/reg-notes.def b/gcc/reg-notes.def index a542990cde2..d83fc45ef72 100644 --- a/gcc/reg-notes.def +++ b/gcc/reg-notes.def @@ -232,3 +232,10 @@ REG_NOTE (STACK_CHECK) The decl might not be available in the call due to splitting of the call insn. This note is a SYMBOL_REF. */ REG_NOTE (CALL_DECL) + +/* Indicate that a call should not be verified for control-flow consistency. + The target address of the call is assumed as a valid address and no check + to validate a branch to the target address is needed. The call is marked + when a called function has a 'notrack' attribute. This note is used by the + compiler when the option -fcf-protection=branch is specified. */ +REG_NOTE (CALL_NOCF_CHECK) diff --git a/gcc/toplev.c b/gcc/toplev.c index ac9d2fc18bb..8c45e1df2af 100644 --- a/gcc/toplev.c +++ b/gcc/toplev.c @@ -1277,6 +1277,32 @@ process_options (void) "-floop-parallelize-all)"); #endif + if (flag_cf_protection != CF_NONE + && !(flag_cf_protection & CF_SET)) + { + if (flag_cf_protection == CF_FULL) + { + error_at (UNKNOWN_LOCATION, + "%<-fcf-protection=full%> is not supported for this " + "target"); + flag_cf_protection = CF_NONE; + } + if (flag_cf_protection == CF_BRANCH) + { + error_at (UNKNOWN_LOCATION, + "%<-fcf-protection=branch%> is not supported for this " + "target"); + flag_cf_protection = CF_NONE; + } + if (flag_cf_protection == CF_RETURN) + { + error_at (UNKNOWN_LOCATION, + "%<-fcf-protection=return%> is not supported for this " + "target"); + flag_cf_protection = CF_NONE; + } + } + if (flag_check_pointer_bounds) { if (targetm.chkp_bound_mode () == VOIDmode)