From 3dcea658c9e2ac84f0726e679fd7d3b14f9106f0 Mon Sep 17 00:00:00 2001 From: "H.J. Lu" Date: Mon, 3 Feb 2020 10:22:57 -0800 Subject: [PATCH] x86: Add UNSPECV_PATCHABLE_AREA Currently patchable area is at the wrong place. It is placed immediately after function label, before both .cfi_startproc and ENDBR. This patch adds UNSPECV_PATCHABLE_AREA for pseudo patchable area instruction and changes ENDBR insertion pass to also insert patchable area instruction. TARGET_ASM_PRINT_PATCHABLE_FUNCTION_ENTRY is defined to avoid placing patchable area before .cfi_startproc and ENDBR. gcc/ PR target/93492 * config/i386/i386-features.c (rest_of_insert_endbranch): Renamed to ... (rest_of_insert_endbr_and_patchable_area): Change return type to void. Add need_endbr and patchable_area_size arguments. Don't call timevar_push nor timevar_pop. Replace endbr_queued_at_entrance with insn_queued_at_entrance. Insert UNSPECV_PATCHABLE_AREA for patchable area. (pass_data_insert_endbranch): Renamed to ... (pass_data_insert_endbr_and_patchable_area): This. Change pass name to endbr_and_patchable_area. (pass_insert_endbranch): Renamed to ... (pass_insert_endbr_and_patchable_area): This. Add need_endbr and patchable_area_size;. (pass_insert_endbr_and_patchable_area::gate): Set and check need_endbr and patchable_area_size. (pass_insert_endbr_and_patchable_area::execute): Call timevar_push and timevar_pop. Pass need_endbr and patchable_area_size to rest_of_insert_endbr_and_patchable_area. (make_pass_insert_endbranch): Renamed to ... (make_pass_insert_endbr_and_patchable_area): This. * config/i386/i386-passes.def: Replace pass_insert_endbranch with pass_insert_endbr_and_patchable_area. * config/i386/i386-protos.h (ix86_output_patchable_area): New. (make_pass_insert_endbranch): Renamed to ... (make_pass_insert_endbr_and_patchable_area): This. * config/i386/i386.c (ix86_asm_output_function_label): Set function_label_emitted to true. (ix86_print_patchable_function_entry): New function. (ix86_output_patchable_area): Likewise. (x86_function_profiler): Replace endbr_queued_at_entrance with insn_queued_at_entrance. Generate ENDBR only for TYPE_ENDBR. Call ix86_output_patchable_area to generate patchable area if needed. (TARGET_ASM_PRINT_PATCHABLE_FUNCTION_ENTRY): New. * config/i386/i386.h (queued_insn_type): New. (machine_function): Add function_label_emitted. Replace endbr_queued_at_entrance with insn_queued_at_entrance. * config/i386/i386.md (UNSPECV_PATCHABLE_AREA): New. (patchable_area): New. gcc/testsuite/ PR target/93492 * gcc.target/i386/pr93492-1.c: New test. * gcc.target/i386/pr93492-2.c: Likewise. * gcc.target/i386/pr93492-3.c: Likewise. * gcc.target/i386/pr93492-4.c: Likewise. * gcc.target/i386/pr93492-5.c: Likewise. --- gcc/config/i386/i386-features.c | 142 ++++++++++++++-------- gcc/config/i386/i386-passes.def | 2 +- gcc/config/i386/i386-protos.h | 5 +- gcc/config/i386/i386.c | 51 +++++++- gcc/config/i386/i386.h | 14 ++- gcc/config/i386/i386.md | 17 +++ gcc/testsuite/gcc.target/i386/pr93492-1.c | 73 +++++++++++ gcc/testsuite/gcc.target/i386/pr93492-2.c | 12 ++ gcc/testsuite/gcc.target/i386/pr93492-3.c | 13 ++ gcc/testsuite/gcc.target/i386/pr93492-4.c | 11 ++ gcc/testsuite/gcc.target/i386/pr93492-5.c | 12 ++ 11 files changed, 296 insertions(+), 56 deletions(-) create mode 100644 gcc/testsuite/gcc.target/i386/pr93492-1.c create mode 100644 gcc/testsuite/gcc.target/i386/pr93492-2.c create mode 100644 gcc/testsuite/gcc.target/i386/pr93492-3.c create mode 100644 gcc/testsuite/gcc.target/i386/pr93492-4.c create mode 100644 gcc/testsuite/gcc.target/i386/pr93492-5.c diff --git a/gcc/config/i386/i386-features.c b/gcc/config/i386/i386-features.c index b9b764c092a..535fc7e981d 100644 --- a/gcc/config/i386/i386-features.c +++ b/gcc/config/i386/i386-features.c @@ -1946,48 +1946,83 @@ make_pass_stv (gcc::context *ctxt) return new pass_stv (ctxt); } -/* Inserting ENDBRANCH instructions. */ +/* Inserting ENDBR and pseudo patchable-area instructions. */ -static unsigned int -rest_of_insert_endbranch (void) +static void +rest_of_insert_endbr_and_patchable_area (bool need_endbr, + unsigned int patchable_area_size) { - timevar_push (TV_MACH_DEP); - - rtx cet_eb; + rtx endbr; rtx_insn *insn; + rtx_insn *endbr_insn = NULL; basic_block bb; - /* Currently emit EB if it's a tracking function, i.e. 'nocf_check' is - absent among function attributes. Later an optimization will be - introduced to make analysis if an address of a static function is - taken. A static function whose address is not taken will get a - nocf_check attribute. This will allow to reduce the number of EB. */ - - if (!lookup_attribute ("nocf_check", - TYPE_ATTRIBUTES (TREE_TYPE (cfun->decl))) - && (!flag_manual_endbr - || lookup_attribute ("cf_check", - DECL_ATTRIBUTES (cfun->decl))) - && (!cgraph_node::get (cfun->decl)->only_called_directly_p () - || ix86_cmodel == CM_LARGE - || ix86_cmodel == CM_LARGE_PIC - || flag_force_indirect_call - || (TARGET_DLLIMPORT_DECL_ATTRIBUTES - && DECL_DLLIMPORT_P (cfun->decl)))) - { - /* Queue ENDBR insertion to x86_function_profiler. */ + if (need_endbr) + { + /* Currently emit EB if it's a tracking function, i.e. 'nocf_check' + is absent among function attributes. Later an optimization will + be introduced to make analysis if an address of a static function + is taken. A static function whose address is not taken will get + a nocf_check attribute. This will allow to reduce the number of + EB. */ + if (!lookup_attribute ("nocf_check", + TYPE_ATTRIBUTES (TREE_TYPE (cfun->decl))) + && (!flag_manual_endbr + || lookup_attribute ("cf_check", + DECL_ATTRIBUTES (cfun->decl))) + && (!cgraph_node::get (cfun->decl)->only_called_directly_p () + || ix86_cmodel == CM_LARGE + || ix86_cmodel == CM_LARGE_PIC + || flag_force_indirect_call + || (TARGET_DLLIMPORT_DECL_ATTRIBUTES + && DECL_DLLIMPORT_P (cfun->decl)))) + { + if (crtl->profile && flag_fentry) + { + /* Queue ENDBR insertion to x86_function_profiler. + NB: Any patchable-area insn will be inserted after + ENDBR. */ + cfun->machine->insn_queued_at_entrance = TYPE_ENDBR; + } + else + { + endbr = gen_nop_endbr (); + bb = ENTRY_BLOCK_PTR_FOR_FN (cfun)->next_bb; + rtx_insn *insn = BB_HEAD (bb); + endbr_insn = emit_insn_before (endbr, insn); + } + } + } + + if (patchable_area_size) + { if (crtl->profile && flag_fentry) - cfun->machine->endbr_queued_at_entrance = true; + { + /* Queue patchable-area insertion to x86_function_profiler. + NB: If there is a queued ENDBR, x86_function_profiler + will also handle patchable-area. */ + if (!cfun->machine->insn_queued_at_entrance) + cfun->machine->insn_queued_at_entrance = TYPE_PATCHABLE_AREA; + } else { - cet_eb = gen_nop_endbr (); - - bb = ENTRY_BLOCK_PTR_FOR_FN (cfun)->next_bb; - insn = BB_HEAD (bb); - emit_insn_before (cet_eb, insn); + rtx patchable_area + = gen_patchable_area (GEN_INT (patchable_area_size), + GEN_INT (crtl->patch_area_entry == 0)); + if (endbr_insn) + emit_insn_after (patchable_area, endbr_insn); + else + { + bb = ENTRY_BLOCK_PTR_FOR_FN (cfun)->next_bb; + insn = BB_HEAD (bb); + emit_insn_before (patchable_area, insn); + } } } + if (!need_endbr) + return; + bb = 0; FOR_EACH_BB_FN (bb, cfun) { @@ -1996,7 +2031,6 @@ rest_of_insert_endbranch (void) { if (CALL_P (insn)) { - bool need_endbr; need_endbr = find_reg_note (insn, REG_SETJMP, NULL) != NULL; if (!need_endbr && !SIBLING_CALL_P (insn)) { @@ -2027,8 +2061,8 @@ rest_of_insert_endbranch (void) /* Generate ENDBRANCH after CALL, which can return more than twice, setjmp-like functions. */ - cet_eb = gen_nop_endbr (); - emit_insn_after_setloc (cet_eb, insn, INSN_LOCATION (insn)); + endbr = gen_nop_endbr (); + emit_insn_after_setloc (endbr, insn, INSN_LOCATION (insn)); continue; } @@ -2058,31 +2092,30 @@ rest_of_insert_endbranch (void) dest_blk = e->dest; insn = BB_HEAD (dest_blk); gcc_assert (LABEL_P (insn)); - cet_eb = gen_nop_endbr (); - emit_insn_after (cet_eb, insn); + endbr = gen_nop_endbr (); + emit_insn_after (endbr, insn); } continue; } if (LABEL_P (insn) && LABEL_PRESERVE_P (insn)) { - cet_eb = gen_nop_endbr (); - emit_insn_after (cet_eb, insn); + endbr = gen_nop_endbr (); + emit_insn_after (endbr, insn); continue; } } } - timevar_pop (TV_MACH_DEP); - return 0; + return; } namespace { -const pass_data pass_data_insert_endbranch = +const pass_data pass_data_insert_endbr_and_patchable_area = { RTL_PASS, /* type. */ - "cet", /* name. */ + "endbr_and_patchable_area", /* name. */ OPTGROUP_NONE, /* optinfo_flags. */ TV_MACH_DEP, /* tv_id. */ 0, /* properties_required. */ @@ -2092,32 +2125,41 @@ const pass_data pass_data_insert_endbranch = 0, /* todo_flags_finish. */ }; -class pass_insert_endbranch : public rtl_opt_pass +class pass_insert_endbr_and_patchable_area : public rtl_opt_pass { public: - pass_insert_endbranch (gcc::context *ctxt) - : rtl_opt_pass (pass_data_insert_endbranch, ctxt) + pass_insert_endbr_and_patchable_area (gcc::context *ctxt) + : rtl_opt_pass (pass_data_insert_endbr_and_patchable_area, ctxt) {} /* opt_pass methods: */ virtual bool gate (function *) { - return ((flag_cf_protection & CF_BRANCH)); + need_endbr = (flag_cf_protection & CF_BRANCH) != 0; + patchable_area_size = crtl->patch_area_size - crtl->patch_area_entry; + return need_endbr || patchable_area_size; } virtual unsigned int execute (function *) { - return rest_of_insert_endbranch (); + timevar_push (TV_MACH_DEP); + rest_of_insert_endbr_and_patchable_area (need_endbr, + patchable_area_size); + timevar_pop (TV_MACH_DEP); + return 0; } -}; // class pass_insert_endbranch +private: + bool need_endbr; + unsigned int patchable_area_size; +}; // class pass_insert_endbr_and_patchable_area } // anon namespace rtl_opt_pass * -make_pass_insert_endbranch (gcc::context *ctxt) +make_pass_insert_endbr_and_patchable_area (gcc::context *ctxt) { - return new pass_insert_endbranch (ctxt); + return new pass_insert_endbr_and_patchable_area (ctxt); } /* At entry of the nearest common dominator for basic blocks with diff --git a/gcc/config/i386/i386-passes.def b/gcc/config/i386/i386-passes.def index 41386a13d88..d83c7b956b1 100644 --- a/gcc/config/i386/i386-passes.def +++ b/gcc/config/i386/i386-passes.def @@ -30,6 +30,6 @@ along with GCC; see the file COPYING3. If not see CONSTM1_RTX generated by the STV pass can be CSEed. */ INSERT_PASS_BEFORE (pass_cse2, 1, pass_stv, true /* timode_p */); - INSERT_PASS_BEFORE (pass_shorten_branches, 1, pass_insert_endbranch); + INSERT_PASS_BEFORE (pass_shorten_branches, 1, pass_insert_endbr_and_patchable_area); INSERT_PASS_AFTER (pass_combine, 1, pass_remove_partial_avx_dependency); diff --git a/gcc/config/i386/i386-protos.h b/gcc/config/i386/i386-protos.h index 39fcaa0ad5f..e5574496bb7 100644 --- a/gcc/config/i386/i386-protos.h +++ b/gcc/config/i386/i386-protos.h @@ -89,6 +89,8 @@ extern const char *output_fp_compare (rtx_insn *, rtx*, bool, bool); extern const char *output_adjust_stack_and_probe (rtx); extern const char *output_probe_stack_range (rtx, rtx); +extern void ix86_output_patchable_area (unsigned int, bool); + extern void ix86_expand_clear (rtx); extern void ix86_expand_move (machine_mode, rtx[]); extern void ix86_expand_vector_move (machine_mode, rtx[]); @@ -378,6 +380,7 @@ class rtl_opt_pass; extern rtl_opt_pass *make_pass_insert_vzeroupper (gcc::context *); extern rtl_opt_pass *make_pass_stv (gcc::context *); -extern rtl_opt_pass *make_pass_insert_endbranch (gcc::context *); +extern rtl_opt_pass *make_pass_insert_endbr_and_patchable_area + (gcc::context *); extern rtl_opt_pass *make_pass_remove_partial_avx_dependency (gcc::context *); diff --git a/gcc/config/i386/i386.c b/gcc/config/i386/i386.c index 060e2df62ea..3b776c08a22 100644 --- a/gcc/config/i386/i386.c +++ b/gcc/config/i386/i386.c @@ -1562,6 +1562,9 @@ ix86_asm_output_function_label (FILE *asm_out_file, const char *fname, { bool is_ms_hook = ix86_function_ms_hook_prologue (decl); + if (cfun) + cfun->machine->function_label_emitted = true; + if (is_ms_hook) { int i, filler_count = (TARGET_64BIT ? 32 : 16); @@ -9367,6 +9370,38 @@ ix86_output_function_epilogue (FILE *file ATTRIBUTE_UNUSED) } } +/* Implement TARGET_ASM_PRINT_PATCHABLE_FUNCTION_ENTRY. */ + +void +ix86_print_patchable_function_entry (FILE *file, + unsigned HOST_WIDE_INT patch_area_size, + bool record_p) +{ + if (cfun->machine->function_label_emitted) + { + /* NB: When ix86_print_patchable_function_entry is called after + function table has been emitted, we have inserted or queued + a pseudo UNSPECV_PATCHABLE_AREA instruction at the proper + place. There is nothing to do here. */ + return; + } + + default_print_patchable_function_entry (file, patch_area_size, + record_p); +} + +/* Output patchable area. NB: default_print_patchable_function_entry + isn't available in i386.md. */ + +void +ix86_output_patchable_area (unsigned int patch_area_size, + bool record_p) +{ + default_print_patchable_function_entry (asm_out_file, + patch_area_size, + record_p); +} + /* Return a scratch register to use in the split stack prologue. The split stack prologue is used for -fsplit-stack. It is the first instructions in the function, even before the regular prologue. @@ -20418,8 +20453,16 @@ current_fentry_section (const char **name) void x86_function_profiler (FILE *file, int labelno ATTRIBUTE_UNUSED) { - if (cfun->machine->endbr_queued_at_entrance) - fprintf (file, "\t%s\n", TARGET_64BIT ? "endbr64" : "endbr32"); + if (cfun->machine->insn_queued_at_entrance) + { + if (cfun->machine->insn_queued_at_entrance == TYPE_ENDBR) + fprintf (file, "\t%s\n", TARGET_64BIT ? "endbr64" : "endbr32"); + unsigned int patch_area_size + = crtl->patch_area_size - crtl->patch_area_entry; + if (patch_area_size) + ix86_output_patchable_area (patch_area_size, + crtl->patch_area_entry == 0); + } const char *mcount_name = MCOUNT_NAME; @@ -23019,6 +23062,10 @@ ix86_run_selftests (void) #undef TARGET_ASM_FUNCTION_EPILOGUE #define TARGET_ASM_FUNCTION_EPILOGUE ix86_output_function_epilogue +#undef TARGET_ASM_PRINT_PATCHABLE_FUNCTION_ENTRY +#define TARGET_ASM_PRINT_PATCHABLE_FUNCTION_ENTRY \ + ix86_print_patchable_function_entry + #undef TARGET_ENCODE_SECTION_INFO #ifndef SUBTARGET_ENCODE_SECTION_INFO #define TARGET_ENCODE_SECTION_INFO ix86_encode_section_info diff --git a/gcc/config/i386/i386.h b/gcc/config/i386/i386.h index 5bae257b435..5d0ea16a0bb 100644 --- a/gcc/config/i386/i386.h +++ b/gcc/config/i386/i386.h @@ -2761,6 +2761,13 @@ enum function_type TYPE_EXCEPTION }; +enum queued_insn_type +{ + TYPE_NONE = 0, + TYPE_ENDBR, + TYPE_PATCHABLE_AREA +}; + struct GTY(()) machine_function { struct stack_local_entry *stack_locals; int varargs_gpr_size; @@ -2851,8 +2858,11 @@ struct GTY(()) machine_function { /* Nonzero if the function places outgoing arguments on stack. */ BOOL_BITFIELD outgoing_args_on_stack : 1; - /* If true, ENDBR is queued at function entrance. */ - BOOL_BITFIELD endbr_queued_at_entrance : 1; + /* If true, ENDBR or patchable area is queued at function entrance. */ + ENUM_BITFIELD(queued_insn_type) insn_queued_at_entrance : 2; + + /* If true, the function label has been emitted. */ + BOOL_BITFIELD function_label_emitted : 1; /* True if the function needs a stack frame. */ BOOL_BITFIELD stack_frame_required : 1; diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md index 9db7469dfcc..4d455584f09 100644 --- a/gcc/config/i386/i386.md +++ b/gcc/config/i386/i386.md @@ -307,6 +307,9 @@ ;; For SERIALIZE support UNSPECV_SERIALIZE + + ;; For patchable area support + UNSPECV_PATCHABLE_AREA ]) ;; Constants to represent rounding modes in the ROUND instruction @@ -21529,6 +21532,20 @@ [(set_attr "type" "other") (set_attr "length" "3")]) +(define_insn "patchable_area" + [(unspec_volatile [(match_operand 0 "const_int_operand") + (match_operand 1 "const_int_operand")] + UNSPECV_PATCHABLE_AREA)] + "" +{ + ix86_output_patchable_area (INTVAL (operands[0]), + INTVAL (operands[1]) != 0); + return ""; +} + [(set (attr "length") (symbol_ref "INTVAL (operands[0])")) + (set_attr "length_immediate" "0") + (set_attr "modrm" "0")]) + (include "mmx.md") (include "sse.md") (include "sync.md") diff --git a/gcc/testsuite/gcc.target/i386/pr93492-1.c b/gcc/testsuite/gcc.target/i386/pr93492-1.c new file mode 100644 index 00000000000..f978d2e5220 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/pr93492-1.c @@ -0,0 +1,73 @@ +/* { dg-do "compile" } */ +/* { dg-options "-O1 -fcf-protection -mmanual-endbr" } */ +/* { dg-final { check-function-bodies "**" "" } } */ + +/* Note: this test only checks the instructions in the function bodies, + not the placement of the patch label or nops before the function. */ + +/* +**f10_none: +** nop +** ret +*/ +void +__attribute__ ((nocf_check,patchable_function_entry (1, 0))) +f10_none (void) +{ +} + +/* +**f10_endbr: +** endbr(32|64) +** nop +** ret +*/ +void +__attribute__ ((cf_check,patchable_function_entry (1, 0))) +f10_endbr (void) +{ +} + +/* +**f11_none: +** ret +*/ +void +__attribute__ ((nocf_check,patchable_function_entry (1, 1))) +f11_none (void) +{ +} + +/* +**f11_endbr: +** endbr(32|64) +** ret +*/ +void +__attribute__ ((cf_check,patchable_function_entry (1, 1))) +f11_endbr (void) +{ +} + +/* +**f21_none: +** nop +** ret +*/ +void +__attribute__ ((nocf_check,patchable_function_entry (2, 1))) +f21_none (void) +{ +} + +/* +**f21_endbr: +** endbr(32|64) +** nop +** ret +*/ +void +__attribute__ ((cf_check,patchable_function_entry (2, 1))) +f21_endbr (void) +{ +} diff --git a/gcc/testsuite/gcc.target/i386/pr93492-2.c b/gcc/testsuite/gcc.target/i386/pr93492-2.c new file mode 100644 index 00000000000..ec26d4cc367 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/pr93492-2.c @@ -0,0 +1,12 @@ +/* { dg-do "compile" } */ +/* { dg-options "-O1 -fcf-protection -mmanual-endbr -fasynchronous-unwind-tables" } */ + +/* Test the placement of the .LPFE1 label. */ + +void +__attribute__ ((cf_check,patchable_function_entry (1, 0))) +f10_endbr (void) +{ +} + +/* { dg-final { scan-assembler "\t\.cfi_startproc\n\tendbr(32|64)\n.*\.LPFE1:\n\tnop\n\tret\n" } } */ diff --git a/gcc/testsuite/gcc.target/i386/pr93492-3.c b/gcc/testsuite/gcc.target/i386/pr93492-3.c new file mode 100644 index 00000000000..1f03c627120 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/pr93492-3.c @@ -0,0 +1,13 @@ +/* { dg-do "compile" } */ +/* { dg-require-effective-target mfentry } */ +/* { dg-options "-O1 -fcf-protection -mmanual-endbr -mfentry -pg -fasynchronous-unwind-tables" } */ + +/* Test the placement of the .LPFE1 label. */ + +void +__attribute__ ((cf_check,patchable_function_entry (1, 0))) +f10_endbr (void) +{ +} + +/* { dg-final { scan-assembler "\t\.cfi_startproc\n\tendbr(32|64)\n.*\.LPFE1:\n\tnop\n1:\tcall\t__fentry__\n\tret\n" } } */ diff --git a/gcc/testsuite/gcc.target/i386/pr93492-4.c b/gcc/testsuite/gcc.target/i386/pr93492-4.c new file mode 100644 index 00000000000..d193df8e66d --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/pr93492-4.c @@ -0,0 +1,11 @@ +/* { dg-do "compile" } */ +/* { dg-options "-O1 -fpatchable-function-entry=1 -fasynchronous-unwind-tables" } */ + +/* Test the placement of the .LPFE1 label. */ + +void +foo (void) +{ +} + +/* { dg-final { scan-assembler "\t\.cfi_startproc\n.*\.LPFE1:\n\tnop\n\tret\n" } } */ diff --git a/gcc/testsuite/gcc.target/i386/pr93492-5.c b/gcc/testsuite/gcc.target/i386/pr93492-5.c new file mode 100644 index 00000000000..d04077c6007 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/pr93492-5.c @@ -0,0 +1,12 @@ +/* { dg-do "compile" } */ +/* { dg-require-effective-target mfentry } */ +/* { dg-options "-O1 -fpatchable-function-entry=1 -mfentry -pg -fasynchronous-unwind-tables" } */ + +/* Test the placement of the .LPFE1 label. */ + +void +foo (void) +{ +} + +/* { dg-final { scan-assembler "\t\.cfi_startproc\n.*\.LPFE1:\n\tnop\n1:\tcall\t__fentry__\n\tret\n" } } */ -- 2.30.2