From c7defc5386cc53a4abbb7c53a924cdac3f16aa33 Mon Sep 17 00:00:00 2001 From: Indu Bhagat Date: Mon, 15 Jan 2024 01:00:31 -0800 Subject: gas: x86: synthesize CFI for hand-written asm This patch adds support in GAS to create generic GAS instructions (a.k.a., the ginsn) for the x86 backend (AMD64 ABI only at this time). Using this ginsn infrastructure, GAS can then synthesize CFI for hand-written asm for x86_64. A ginsn is a target-independent representation of the machine instructions. One machine instruction may need one or more ginsn. This patch also adds skeleton support for printing ginsn in the listing output for debugging purposes. Since the current use-case of ginsn is to synthesize CFI, the x86 target needs to generate ginsns necessary for the following machine instructions only: - All change of flow instructions, including all conditional and unconditional branches, call and return from functions. - All register saves and unsaves to the stack. - All instructions affecting the two registers that could potentially be used as the base register for CFA tracking. For SCFI, the base register for CFA tracking is limited to REG_SP and REG_FP only for now. The representation of ginsn is kept simple: - GAS instruction has GINSN_NUM_SRC_OPNDS (defined to be 2 at this time) number of source operands and one destination operand at this time. - GAS instruction uses DWARF register numbers in its representation and does not track register size. - GAS instructions carry location information (file name and line number). - GAS instructions are ID's with a natural number in order of their addtion to the list. This can be used as a proxy for the static program order of the corresponding machine instructions. Note that, GAS instruction (ginsn) format does not support GINSN_TYPE_PUSH and GINSN_TYPE_POP. Some architectures, like aarch64, do not have push and pop instructions, but rather STP/LDP/STR/LDR etc. instructions. Further these instructions have a variety of addressing modes, like offset, pre-indexing and post-indexing etc. Among other things, one of differences in these addressing modes is _when_ the addr register is updated with the result of the address calculation: before or after the memory operation. To best support such needs, the generic instructions like GINSN_TYPE_LOAD, GINSN_TYPE_STORE together with GINSN_TYPE_ADD, and GINSN_TYPE_SUB may be used. The functionality provided in ginsn.c and scfi.c is compiled in when a target defines TARGET_USE_SCFI and TARGET_USE_GINSN. This can be revisited later when there are other use-cases of creating ginsn's in GAS, apart from the current use-case of synthesizing CFI for hand-written asm. Support is added only for System V AMD64 ABI for ELF at this time. If the user enables SCFI with --32, GAS issues an error: "Fatal error: SCFI is not supported for this ABI" For synthesizing (DWARF) CFI, the SCFI machinery requires the programmer to adhere to some pre-requisites for their asm: - Hand-written asm block must begin with a .type foo, @function It is highly recommended to, additionally, also ensure that: - Hand-written asm block ends with a .size foo, .-foo The SCFI machinery encodes some rules which align with the standard calling convention specified by the ABI. Apart from the rules, the SCFI machinery employs some heuristics. For example: - The base register for CFA tracking may be either REG_SP or REG_FP. - If the base register for CFA tracking is REG_SP, the precise amount of stack usage (and hence, the value of REG_SP) must be known at all times. - If using dynamic stack allocation, the function must switch to FP-based CFA. This means using instructions like the following (in AMD64) in prologue: pushq %rbp movq %rsp, %rbp and analogous instructions in epilogue. - Save and Restore of callee-saved registers must be symmetrical. However, the SCFI machinery at this time only warns if any such asymmetry is seen. These heuristics/rules are architecture-independent and are meant to employed for all architectures/ABIs using SCFI in the future. gas/ * Makefile.am: Add new files. * Makefile.in: Regenerated. * as.c (defined): Handle documentation and listing option for ginsns and SCFI. * config/obj-elf.c (obj_elf_size): Invoke ginsn_data_end. (obj_elf_type): Invoke ginsn_data_begin. * config/tc-i386.c (x86_scfi_callee_saved_p): New function. (ginsn_prefix_66H_p): Likewise. (ginsn_dw2_regnum): Likewise. (x86_ginsn_addsub_reg_mem): Likewise. (x86_ginsn_addsub_mem_reg): Likewise. (x86_ginsn_alu_imm): Likewise. (x86_ginsn_move): Likewise. (x86_ginsn_lea): Likewise. (x86_ginsn_jump): Likewise. (x86_ginsn_jump_cond): Likewise. (x86_ginsn_enter): Likewise. (x86_ginsn_safe_to_skip): Likewise. (x86_ginsn_unhandled): Likewise. (x86_ginsn_new): New functionality to generate ginsns. (md_assemble): Invoke x86_ginsn_new. (s_insn): Likewise. (i386_target_format): Add hard error for usage of SCFI with non AMD64 ABIs. * config/tc-i386.h (TARGET_USE_GINSN): New definition. (TARGET_USE_SCFI): Likewise. (SCFI_MAX_REG_ID): Likewise. (REG_FP): Likewise. (REG_SP): Likewise. (SCFI_INIT_CFA_OFFSET): Likewise. (SCFI_CALLEE_SAVED_REG_P): Likewise. (x86_scfi_callee_saved_p): Likewise. * gas/listing.h (LISTING_GINSN_SCFI): New define for ginsn and SCFI. * gas/read.c (read_a_source_file): Close SCFI processing at end of file read. * gas/scfidw2gen.c (scfi_process_cfi_label): Add implementation. (scfi_process_cfi_signal_frame): Likewise. * subsegs.h (struct frch_ginsn_data): New forward declaration. (struct frchain): New member for ginsn data. * gas/subsegs.c (subseg_set_rest): Initialize the new member. * symbols.c (colon): Invoke ginsn_frob_label to convey user-defined labels to ginsn infrastructure. * ginsn.c: New file. * ginsn.h: New file. * scfi.c: New file. * scfi.h: New file. --- gas/config/obj-elf.c | 18 + gas/config/tc-i386.c | 1113 ++++++++++++++++++++++++++++++++++++++++++++++++++ gas/config/tc-i386.h | 21 + 3 files changed, 1152 insertions(+) (limited to 'gas/config') diff --git a/gas/config/obj-elf.c b/gas/config/obj-elf.c index 1f34f5b..00c6e38 100644 --- a/gas/config/obj-elf.c +++ b/gas/config/obj-elf.c @@ -24,6 +24,7 @@ #include "subsegs.h" #include "obstack.h" #include "dwarf2dbg.h" +#include "ginsn.h" #ifndef ECOFF_DEBUGGING #define ECOFF_DEBUGGING 0 @@ -2311,6 +2312,13 @@ obj_elf_size (int ignore ATTRIBUTE_UNUSED) symbol_get_obj (sym)->size = XNEW (expressionS); *symbol_get_obj (sym)->size = exp; } + + /* If the symbol in the directive matches the current function being + processed, indicate end of the current stream of ginsns. */ + if (flag_synth_cfi + && S_IS_FUNCTION (sym) && sym == ginsn_data_func_symbol ()) + ginsn_data_end (symbol_temp_new_now ()); + demand_empty_rest_of_line (); } @@ -2499,6 +2507,16 @@ obj_elf_type (int ignore ATTRIBUTE_UNUSED) elfsym->symbol.flags &= ~mask; } + if (S_IS_FUNCTION (sym) && flag_synth_cfi) + { + /* When using SCFI, .type directive indicates start of a new FDE for SCFI + processing. So, we must first demarcate the previous block of ginsns, + if any, to mark the end of a previous FDE. */ + if (frchain_now->frch_ginsn_data) + ginsn_data_end (symbol_temp_new_now ()); + ginsn_data_begin (sym); + } + demand_empty_rest_of_line (); } diff --git a/gas/config/tc-i386.c b/gas/config/tc-i386.c index b25fa37..455e5a3 100644 --- a/gas/config/tc-i386.c +++ b/gas/config/tc-i386.c @@ -30,6 +30,7 @@ #include "subsegs.h" #include "dwarf2dbg.h" #include "dw2gencfi.h" +#include "scfi.h" #include "gen-sframe.h" #include "sframe.h" #include "elf/x86-64.h" @@ -5327,6 +5328,1095 @@ static INLINE bool may_need_pass2 (const insn_template *t) && t->base_opcode == 0x63); } +#if defined (OBJ_MAYBE_ELF) || defined (OBJ_ELF) + +/* DWARF register number for EFLAGS. Used for pushf/popf insns. */ +#define GINSN_DW2_REGNUM_EFLAGS 49 +/* DWARF register number for RSI. Used as dummy value when RegIP/RegIZ. */ +#define GINSN_DW2_REGNUM_RSI_DUMMY 4 + +/* Identify the callee-saved registers in System V AMD64 ABI. */ + +bool +x86_scfi_callee_saved_p (unsigned int dw2reg_num) +{ + if (dw2reg_num == 3 /* rbx. */ + || dw2reg_num == REG_FP /* rbp. */ + || dw2reg_num == REG_SP /* rsp. */ + || (dw2reg_num >= 12 && dw2reg_num <= 15) /* r12 - r15. */) + return true; + + return false; +} + +/* Check whether an instruction prefix which affects operation size + accompanies. For insns in the legacy space, setting REX.W takes precedence + over the operand-size prefix (66H) when both are used. + + The current users of this API are in the handlers for PUSH, POP or other + instructions which affect the stack pointer implicitly: the operation size + (16, 32, or 64 bits) determines the amount by which the stack pointer is + incremented / decremented (2, 4 or 8). */ + +static bool +ginsn_opsize_prefix_p (void) +{ + return (!(i.prefix[REX_PREFIX] & REX_W) && i.prefix[DATA_PREFIX]); +} + +/* Get the DWARF register number for the given register entry. + For specific byte/word/dword register accesses like al, cl, ah, ch, r8d, + r20w etc., we need to identify the DWARF register number for the + corresponding 8-byte GPR. + + This function is a hack - it relies on relative ordering of reg entries in + the i386_regtab. FIXME - it will be good to allow a more direct way to get + this information. */ + +static unsigned int +ginsn_dw2_regnum (const reg_entry *ireg) +{ + /* PS: Note the data type here as int32_t, because of Dw2Inval (-1). */ + int32_t dwarf_reg = Dw2Inval; + const reg_entry *temp = ireg; + unsigned int idx = 0; + + /* ginsn creation is available for AMD64 abi only ATM. Other flag_code + are not expected. */ + gas_assert (ireg && flag_code == CODE_64BIT); + + /* Watch out for RegIP, RegIZ. These are expected to appear only with + base/index addressing modes. Although creating inaccurate data + dependencies, using a dummy value (lets say volatile register rsi) will + not hurt SCFI. TBD_GINSN_GEN_NOT_SCFI. */ + if (ireg->reg_num == RegIP || ireg->reg_num == RegIZ) + return GINSN_DW2_REGNUM_RSI_DUMMY; + + dwarf_reg = ireg->dw2_regnum[flag_code >> 1]; + + if (dwarf_reg == Dw2Inval) + { + if (ireg <= &i386_regtab[3]) + /* For al, cl, dl, bl, bump over to axl, cxl, dxl, bxl respectively by + adding 8. */ + temp = ireg + 8; + else if (ireg <= &i386_regtab[7]) + /* For ah, ch, dh, bh, bump over to axl, cxl, dxl, bxl respectively by + adding 4. */ + temp = ireg + 4; + else + { + /* The code relies on the relative ordering of the reg entries in + i386_regtab. There are 32 register entries between axl-r31b, + ax-r31w etc. The assertions here ensures the code does not + recurse indefinitely. */ + gas_assert ((temp - &i386_regtab[0]) >= 0); + idx = temp - &i386_regtab[0]; + gas_assert (idx + 32 < i386_regtab_size - 1); + + temp = temp + 32; + } + + dwarf_reg = ginsn_dw2_regnum (temp); + } + + /* Sanity check - failure may indicate state corruption, bad ginsn or + perhaps the i386-reg table and the current function got out of sync. */ + gas_assert (dwarf_reg >= 0); + + return (unsigned int) dwarf_reg; +} + +static ginsnS * +x86_ginsn_addsub_reg_mem (const symbolS *insn_end_sym) +{ + unsigned int dw2_regnum; + unsigned int src1_dw2_regnum; + ginsnS *ginsn = NULL; + ginsnS * (*ginsn_func) (const symbolS *, bool, + enum ginsn_src_type, unsigned int, offsetT, + enum ginsn_src_type, unsigned int, offsetT, + enum ginsn_dst_type, unsigned int, offsetT); + uint16_t opcode = i.tm.base_opcode; + + gas_assert (i.tm.opcode_space == SPACE_BASE + && (opcode == 0x1 || opcode == 0x29)); + ginsn_func = (opcode == 0x1) ? ginsn_new_add : ginsn_new_sub; + + /* op %reg, symbol or even other cases where destination involves indirect + access are unnecessary for SCFI correctness. TBD_GINSN_GEN_NOT_SCFI. */ + if (i.mem_operands) + return ginsn; + + /* op reg, reg/mem. */ + src1_dw2_regnum = ginsn_dw2_regnum (i.op[0].regs); + /* Of interest only when second opnd is not memory. */ + if (i.reg_operands == 2) + { + dw2_regnum = ginsn_dw2_regnum (i.op[1].regs); + ginsn = ginsn_func (insn_end_sym, true, + GINSN_SRC_REG, src1_dw2_regnum, 0, + GINSN_SRC_REG, dw2_regnum, 0, + GINSN_DST_REG, dw2_regnum, 0); + ginsn_set_where (ginsn); + } + + return ginsn; +} + +static ginsnS * +x86_ginsn_addsub_mem_reg (const symbolS *insn_end_sym) +{ + unsigned int dw2_regnum; + unsigned int src1_dw2_regnum; + const reg_entry *mem_reg; + int32_t gdisp = 0; + ginsnS *ginsn = NULL; + ginsnS * (*ginsn_func) (const symbolS *, bool, + enum ginsn_src_type, unsigned int, offsetT, + enum ginsn_src_type, unsigned int, offsetT, + enum ginsn_dst_type, unsigned int, offsetT); + uint16_t opcode = i.tm.base_opcode; + + gas_assert (i.tm.opcode_space == SPACE_BASE + && (opcode == 0x3 || opcode == 0x2b)); + ginsn_func = (opcode == 0x3) ? ginsn_new_add : ginsn_new_sub; + + /* op symbol, %reg. */ + if (i.mem_operands && !i.base_reg && !i.index_reg) + return ginsn; + + /* op reg/mem, %reg. */ + dw2_regnum = ginsn_dw2_regnum (i.op[1].regs); + + if (i.reg_operands == 2) + { + src1_dw2_regnum = ginsn_dw2_regnum (i.op[0].regs); + ginsn = ginsn_func (insn_end_sym, true, + GINSN_SRC_REG, src1_dw2_regnum, 0, + GINSN_SRC_REG, dw2_regnum, 0, + GINSN_DST_REG, dw2_regnum, 0); + ginsn_set_where (ginsn); + } + else if (i.mem_operands) + { + mem_reg = (i.base_reg) ? i.base_reg : i.index_reg; + src1_dw2_regnum = ginsn_dw2_regnum (mem_reg); + if (i.disp_operands == 1) + gdisp = i.op[0].disps->X_add_number; + ginsn = ginsn_func (insn_end_sym, true, + GINSN_SRC_INDIRECT, src1_dw2_regnum, gdisp, + GINSN_SRC_REG, dw2_regnum, 0, + GINSN_DST_REG, dw2_regnum, 0); + ginsn_set_where (ginsn); + } + + return ginsn; +} + +static ginsnS * +x86_ginsn_alu_imm (const symbolS *insn_end_sym) +{ + offsetT src_imm; + unsigned int dw2_regnum; + ginsnS *ginsn = NULL; + enum ginsn_src_type src_type = GINSN_SRC_REG; + enum ginsn_dst_type dst_type = GINSN_DST_REG; + + ginsnS * (*ginsn_func) (const symbolS *, bool, + enum ginsn_src_type, unsigned int, offsetT, + enum ginsn_src_type, unsigned int, offsetT, + enum ginsn_dst_type, unsigned int, offsetT); + + /* FIXME - create ginsn where dest is REG_SP / REG_FP only ? */ + /* Map for insn.tm.extension_opcode + 000 ADD 100 AND + 001 OR 101 SUB + 010 ADC 110 XOR + 011 SBB 111 CMP */ + + /* add/sub/and imm, %reg only at this time for SCFI. + Although all three ('and', 'or' , 'xor') make the destination reg + untraceable, 'and' op is handled but not 'or' / 'xor' because we will look + into supporting the DRAP pattern at some point. Other opcodes ('adc', + 'sbb' and 'cmp') are not generated here either. The ginsn representation + does not have support for the latter three opcodes; GINSN_TYPE_OTHER may + be added for these after x86_ginsn_unhandled () invocation if the + destination register is REG_SP or REG_FP. */ + if (i.tm.extension_opcode == 5) + ginsn_func = ginsn_new_sub; + else if (i.tm.extension_opcode == 4) + ginsn_func = ginsn_new_and; + else if (i.tm.extension_opcode == 0) + ginsn_func = ginsn_new_add; + else + return ginsn; + + /* TBD_GINSN_REPRESENTATION_LIMIT: There is no representation for when a + symbol is used as an operand, like so: + addq $simd_cmp_op+8, %rdx + Skip generating any ginsn for this. */ + if (i.imm_operands == 1 + && i.op[0].imms->X_op != O_constant) + return ginsn; + + /* addq $1, symbol + addq $1, -16(%rbp) + These are not of interest for SCFI. Also, TBD_GINSN_GEN_NOT_SCFI. */ + if (i.mem_operands == 1) + return ginsn; + + gas_assert (i.imm_operands == 1); + src_imm = i.op[0].imms->X_add_number; + /* The second operand may be a register or indirect access. For SCFI, only + the case when the second opnd is a register is interesting. Revisit this + if generating ginsns for a different gen mode TBD_GINSN_GEN_NOT_SCFI. */ + if (i.reg_operands == 1) + { + dw2_regnum = ginsn_dw2_regnum (i.op[1].regs); + /* For ginsn, keep the imm as second src operand. */ + ginsn = ginsn_func (insn_end_sym, true, + src_type, dw2_regnum, 0, + GINSN_SRC_IMM, 0, src_imm, + dst_type, dw2_regnum, 0); + + ginsn_set_where (ginsn); + } + + return ginsn; +} + +/* Create ginsn(s) for MOV operations. + + The generated ginsns corresponding to mov with indirect access to memory + (src or dest) suffer with loss of information: when both index and base + registers are at play, only base register gets conveyed in ginsn. Note + this TBD_GINSN_GEN_NOT_SCFI. */ + +static ginsnS * +x86_ginsn_move (const symbolS *insn_end_sym) +{ + ginsnS *ginsn = NULL; + unsigned int dst_reg; + unsigned int src_reg; + offsetT src_disp = 0; + offsetT dst_disp = 0; + const reg_entry *dst = NULL; + const reg_entry *src = NULL; + uint16_t opcode = i.tm.base_opcode; + enum ginsn_src_type src_type = GINSN_SRC_REG; + enum ginsn_dst_type dst_type = GINSN_DST_REG; + + /* mov %reg, symbol or mov symbol, %reg. + Not of interest for SCFI. Also, TBD_GINSN_GEN_NOT_SCFI. */ + if (i.mem_operands == 1 && !i.base_reg && !i.index_reg) + return ginsn; + + gas_assert (i.tm.opcode_space == SPACE_BASE); + if (opcode == 0x8b || opcode == 0x8a) + { + /* mov disp(%reg), %reg. */ + if (i.mem_operands) + { + src = (i.base_reg) ? i.base_reg : i.index_reg; + if (i.disp_operands == 1) + src_disp = i.op[0].disps->X_add_number; + src_type = GINSN_SRC_INDIRECT; + } + else + src = i.op[0].regs; + + dst = i.op[1].regs; + } + else if (opcode == 0x89 || opcode == 0x88) + { + /* mov %reg, disp(%reg). */ + src = i.op[0].regs; + if (i.mem_operands) + { + dst = (i.base_reg) ? i.base_reg : i.index_reg; + if (i.disp_operands == 1) + dst_disp = i.op[1].disps->X_add_number; + dst_type = GINSN_DST_INDIRECT; + } + else + dst = i.op[1].regs; + } + + src_reg = ginsn_dw2_regnum (src); + dst_reg = ginsn_dw2_regnum (dst); + + ginsn = ginsn_new_mov (insn_end_sym, true, + src_type, src_reg, src_disp, + dst_type, dst_reg, dst_disp); + ginsn_set_where (ginsn); + + return ginsn; +} + +/* Generate appropriate ginsn for lea. + Sub-cases marked with TBD_GINSN_INFO_LOSS indicate some loss of information + in the ginsn. But these are fine for now for GINSN_GEN_SCFI generation + mode. */ + +static ginsnS * +x86_ginsn_lea (const symbolS *insn_end_sym) +{ + offsetT src_disp = 0; + ginsnS *ginsn = NULL; + unsigned int base_reg; + unsigned int index_reg; + offsetT index_scale; + unsigned int dst_reg; + + if (!i.index_reg && !i.base_reg) + { + /* lea symbol, %rN. */ + dst_reg = ginsn_dw2_regnum (i.op[1].regs); + /* TBD_GINSN_INFO_LOSS - Skip encoding information about the symbol. */ + ginsn = ginsn_new_mov (insn_end_sym, false, + GINSN_SRC_IMM, 0xf /* arbitrary const. */, 0, + GINSN_DST_REG, dst_reg, 0); + } + else if (i.base_reg && !i.index_reg) + { + /* lea -0x2(%base),%dst. */ + base_reg = ginsn_dw2_regnum (i.base_reg); + dst_reg = ginsn_dw2_regnum (i.op[1].regs); + + if (i.disp_operands) + src_disp = i.op[0].disps->X_add_number; + + if (src_disp) + /* Generate an ADD ginsn. */ + ginsn = ginsn_new_add (insn_end_sym, true, + GINSN_SRC_REG, base_reg, 0, + GINSN_SRC_IMM, 0, src_disp, + GINSN_DST_REG, dst_reg, 0); + else + /* Generate a MOV ginsn. */ + ginsn = ginsn_new_mov (insn_end_sym, true, + GINSN_SRC_REG, base_reg, 0, + GINSN_DST_REG, dst_reg, 0); + } + else if (!i.base_reg && i.index_reg) + { + /* lea (,%index,imm), %dst. */ + /* TBD_GINSN_INFO_LOSS - There is no explicit ginsn multiply operation, + instead use GINSN_TYPE_OTHER. Also, note that info about displacement + is not carried forward either. But this is fine because + GINSN_TYPE_OTHER will cause SCFI pass to bail out any which way if + dest reg is interesting. */ + index_scale = i.log2_scale_factor; + index_reg = ginsn_dw2_regnum (i.index_reg); + dst_reg = ginsn_dw2_regnum (i.op[1].regs); + ginsn = ginsn_new_other (insn_end_sym, true, + GINSN_SRC_REG, index_reg, + GINSN_SRC_IMM, index_scale, + GINSN_DST_REG, dst_reg); + /* FIXME - It seems to make sense to represent a scale factor of 1 + correctly here (i.e. not as "other", but rather similar to the + base-without- index case above)? */ + } + else + { + /* lea disp(%base,%index,imm) %dst. */ + /* TBD_GINSN_INFO_LOSS - Skip adding information about the disp and imm + for index reg. */ + base_reg = ginsn_dw2_regnum (i.base_reg); + index_reg = ginsn_dw2_regnum (i.index_reg); + dst_reg = ginsn_dw2_regnum (i.op[1].regs); + /* Generate an GINSN_TYPE_OTHER ginsn. */ + ginsn = ginsn_new_other (insn_end_sym, true, + GINSN_SRC_REG, base_reg, + GINSN_SRC_REG, index_reg, + GINSN_DST_REG, dst_reg); + } + + ginsn_set_where (ginsn); + + return ginsn; +} + +static ginsnS * +x86_ginsn_jump (const symbolS *insn_end_sym, bool cond_p) +{ + ginsnS *ginsn = NULL; + const symbolS *src_symbol; + ginsnS * (*ginsn_func) (const symbolS *sym, bool real_p, + enum ginsn_src_type src_type, unsigned int src_reg, + const symbolS *src_ginsn_sym); + + gas_assert (i.disp_operands == 1); + + ginsn_func = cond_p ? ginsn_new_jump_cond : ginsn_new_jump; + if (i.op[0].disps->X_op == O_symbol && !i.op[0].disps->X_add_number) + { + src_symbol = i.op[0].disps->X_add_symbol; + ginsn = ginsn_func (insn_end_sym, true, + GINSN_SRC_SYMBOL, 0, src_symbol); + + ginsn_set_where (ginsn); + } + else + { + /* A non-zero addend in jump/JCC target makes control-flow tracking + difficult. Skip SCFI for now. */ + as_bad (_("SCFI: `%s' insn with non-zero addend to sym not supported"), + cond_p ? "JCC" : "jmp"); + return ginsn; + } + + return ginsn; +} + +static ginsnS * +x86_ginsn_enter (const symbolS *insn_end_sym) +{ + ginsnS *ginsn = NULL; + ginsnS *ginsn_next = NULL; + ginsnS *ginsn_last = NULL; + /* In 64-bit mode, the default stack update size is 8 bytes. */ + int stack_opnd_size = 8; + + gas_assert (i.imm_operands == 2); + + /* For non-zero size operands, bail out as untraceable for SCFI. */ + if (i.op[0].imms->X_op != O_constant || i.op[0].imms->X_add_symbol != 0 + || i.op[1].imms->X_op != O_constant || i.op[1].imms->X_add_symbol != 0) + { + as_bad ("SCFI: enter insn with non-zero operand not supported"); + return ginsn; + } + + /* Check if this is a 16-bit op. */ + if (ginsn_opsize_prefix_p ()) + stack_opnd_size = 2; + + /* If the nesting level is 0, the processor pushes the frame pointer from + the BP/EBP/RBP register onto the stack, copies the current stack + pointer from the SP/ESP/RSP register into the BP/EBP/RBP register, and + loads the SP/ESP/RSP register with the current stack-pointer value + minus the value in the size operand. */ + ginsn = ginsn_new_sub (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_SRC_IMM, 0, stack_opnd_size, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn); + ginsn_next = ginsn_new_store (insn_end_sym, false, + GINSN_SRC_REG, REG_FP, + GINSN_DST_INDIRECT, REG_SP, 0); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn, ginsn_next)); + ginsn_last = ginsn_new_mov (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_DST_REG, REG_FP, 0); + ginsn_set_where (ginsn_last); + gas_assert (!ginsn_link_next (ginsn_next, ginsn_last)); + + return ginsn; +} + +static ginsnS * +x86_ginsn_leave (const symbolS *insn_end_sym) +{ + ginsnS *ginsn = NULL; + ginsnS *ginsn_next = NULL; + ginsnS *ginsn_last = NULL; + /* In 64-bit mode, the default stack update size is 8 bytes. */ + int stack_opnd_size = 8; + + /* Check if this is a 16-bit op. */ + if (ginsn_opsize_prefix_p ()) + stack_opnd_size = 2; + + /* The 'leave' instruction copies the contents of the RBP register + into the RSP register to release all stack space allocated to the + procedure. */ + ginsn = ginsn_new_mov (insn_end_sym, false, + GINSN_SRC_REG, REG_FP, 0, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn); + /* Then it restores the old value of the RBP register from the stack. */ + ginsn_next = ginsn_new_load (insn_end_sym, false, + GINSN_SRC_INDIRECT, REG_SP, 0, + GINSN_DST_REG, REG_FP); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn, ginsn_next)); + ginsn_last = ginsn_new_add (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_SRC_IMM, 0, stack_opnd_size, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn_next, ginsn_last)); + + return ginsn; +} + +/* Check if an instruction is whitelisted. + + Some instructions may appear with REG_SP or REG_FP as destination, because + which they are deemed 'interesting' for SCFI. Whitelist them here if they + do not affect SCFI correctness. */ + +static bool +x86_ginsn_safe_to_skip_p (void) +{ + bool skip_p = false; + uint16_t opcode = i.tm.base_opcode; + + switch (opcode) + { + case 0x80: + case 0x81: + case 0x83: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* cmp imm, reg/rem. */ + if (i.tm.extension_opcode == 7) + skip_p = true; + break; + + case 0x38: + case 0x39: + case 0x3a: + case 0x3b: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* cmp imm/reg/mem, reg/rem. */ + skip_p = true; + break; + + case 0xf6: + case 0xf7: + case 0x84: + case 0x85: + /* test imm/reg/mem, reg/mem. */ + if (i.tm.opcode_space != SPACE_BASE) + break; + skip_p = true; + break; + + default: + break; + } + + return skip_p; +} + +#define X86_GINSN_UNHANDLED_NONE 0 +#define X86_GINSN_UNHANDLED_DEST_REG 1 +#define X86_GINSN_UNHANDLED_CFG 2 +#define X86_GINSN_UNHANDLED_STACKOP 3 +#define X86_GINSN_UNHANDLED_UNEXPECTED 4 + +/* Check the input insn for its impact on the correctness of the synthesized + CFI. Returns an error code to the caller. */ + +static int +x86_ginsn_unhandled (void) +{ + int err = X86_GINSN_UNHANDLED_NONE; + const reg_entry *reg_op; + unsigned int dw2_regnum; + + /* Keep an eye out for instructions affecting control flow. */ + if (i.tm.opcode_modifier.jump) + err = X86_GINSN_UNHANDLED_CFG; + /* Also, for any instructions involving an implicit update to the stack + pointer. */ + else if (i.tm.opcode_modifier.operandconstraint == IMPLICIT_STACK_OP) + err = X86_GINSN_UNHANDLED_STACKOP; + /* Finally, also check if the missed instructions are affecting REG_SP or + REG_FP. The destination operand is the last at all stages of assembly + (due to following AT&T syntax layout in the internal representation). In + case of Intel syntax input, this still remains true as swap_operands () + is done by now. + PS: These checks do not involve index / base reg, as indirect memory + accesses via REG_SP or REG_FP do not affect SCFI correctness. + (Also note these instructions are candidates for other ginsn generation + modes in future. TBD_GINSN_GEN_NOT_SCFI.) */ + else if (i.operands && i.reg_operands + && !(i.flags[i.operands - 1] & Operand_Mem)) + { + reg_op = i.op[i.operands - 1].regs; + if (reg_op) + { + dw2_regnum = ginsn_dw2_regnum (reg_op); + if (dw2_regnum == REG_SP || dw2_regnum == REG_FP) + err = X86_GINSN_UNHANDLED_DEST_REG; + } + else + /* Something unexpected. Indicate to caller. */ + err = X86_GINSN_UNHANDLED_UNEXPECTED; + } + + return err; +} + +/* Generate one or more generic GAS instructions, a.k.a, ginsns for the current + machine instruction. + + Returns the head of linked list of ginsn(s) added, if success; Returns NULL + if failure. + + The input ginsn_gen_mode GMODE determines the set of minimal necessary + ginsns necessary for correctness of any passes applicable for that mode. + For supporting the GINSN_GEN_SCFI generation mode, following is the list of + machine instructions that must be translated into the corresponding ginsns + to ensure correctness of SCFI: + - All instructions affecting the two registers that could potentially + be used as the base register for CFA tracking. For SCFI, the base + register for CFA tracking is limited to REG_SP and REG_FP only for + now. + - All change of flow instructions: conditional and unconditional branches, + call and return from functions. + - All instructions that can potentially be a register save / restore + operation. + - All instructions that perform stack manipulation implicitly: the CALL, + RET, PUSH, POP, ENTER, and LEAVE instructions. + + The function currently supports GINSN_GEN_SCFI ginsn generation mode only. + To support other generation modes will require work on this target-specific + process of creation of ginsns: + - Some of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as + possible starting points. + - Also note that ginsn representation may need enhancements. Specifically, + note some TBD_GINSN_INFO_LOSS and TBD_GINSN_REPRESENTATION_LIMIT markers. + */ + +static ginsnS * +x86_ginsn_new (const symbolS *insn_end_sym, enum ginsn_gen_mode gmode) +{ + int err = 0; + uint16_t opcode; + unsigned int dw2_regnum; + const reg_entry *mem_reg; + ginsnS *ginsn = NULL; + ginsnS *ginsn_next = NULL; + /* In 64-bit mode, the default stack update size is 8 bytes. */ + int stack_opnd_size = 8; + + /* Currently supports generation of selected ginsns, sufficient for + the use-case of SCFI only. */ + if (gmode != GINSN_GEN_SCFI) + return ginsn; + + opcode = i.tm.base_opcode; + + /* Until it is clear how to handle APX NDD and other new opcodes, disallow + them from SCFI. */ + if (is_apx_rex2_encoding () + || (i.tm.opcode_modifier.evex && is_apx_evex_encoding ())) + { + as_bad (_("SCFI: unsupported APX op %#x may cause incorrect CFI"), + opcode); + return ginsn; + } + + switch (opcode) + { + case 0x1: /* add reg, reg/mem. */ + case 0x29: /* sub reg, reg/mem. */ + if (i.tm.opcode_space != SPACE_BASE) + break; + ginsn = x86_ginsn_addsub_reg_mem (insn_end_sym); + break; + + case 0x3: /* add reg/mem, reg. */ + case 0x2b: /* sub reg/mem, reg. */ + if (i.tm.opcode_space != SPACE_BASE) + break; + ginsn = x86_ginsn_addsub_mem_reg (insn_end_sym); + break; + + case 0xa0: /* push fs. */ + case 0xa8: /* push gs. */ + /* push fs / push gs have opcode_space == SPACE_0F. */ + if (i.tm.opcode_space != SPACE_0F) + break; + dw2_regnum = ginsn_dw2_regnum (i.op[0].regs); + /* Check if operation size is 16-bit. */ + if (ginsn_opsize_prefix_p ()) + stack_opnd_size = 2; + ginsn = ginsn_new_sub (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_SRC_IMM, 0, stack_opnd_size, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn); + ginsn_next = ginsn_new_store (insn_end_sym, false, + GINSN_SRC_REG, dw2_regnum, + GINSN_DST_INDIRECT, REG_SP, 0); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn, ginsn_next)); + break; + + case 0xa1: /* pop fs. */ + case 0xa9: /* pop gs. */ + /* pop fs / pop gs have opcode_space == SPACE_0F. */ + if (i.tm.opcode_space != SPACE_0F) + break; + dw2_regnum = ginsn_dw2_regnum (i.op[0].regs); + /* Check if operation size is 16-bit. */ + if (ginsn_opsize_prefix_p ()) + stack_opnd_size = 2; + ginsn = ginsn_new_load (insn_end_sym, false, + GINSN_SRC_INDIRECT, REG_SP, 0, + GINSN_DST_REG, dw2_regnum); + ginsn_set_where (ginsn); + ginsn_next = ginsn_new_add (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_SRC_IMM, 0, stack_opnd_size, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn, ginsn_next)); + break; + + case 0x50 ... 0x57: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* push reg. */ + dw2_regnum = ginsn_dw2_regnum (i.op[0].regs); + /* Check if operation size is 16-bit. */ + if (ginsn_opsize_prefix_p ()) + stack_opnd_size = 2; + ginsn = ginsn_new_sub (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_SRC_IMM, 0, stack_opnd_size, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn); + ginsn_next = ginsn_new_store (insn_end_sym, false, + GINSN_SRC_REG, dw2_regnum, + GINSN_DST_INDIRECT, REG_SP, 0); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn, ginsn_next)); + break; + + case 0x58 ... 0x5f: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* pop reg. */ + dw2_regnum = ginsn_dw2_regnum (i.op[0].regs); + ginsn = ginsn_new_load (insn_end_sym, false, + GINSN_SRC_INDIRECT, REG_SP, 0, + GINSN_DST_REG, dw2_regnum); + ginsn_set_where (ginsn); + /* Check if operation size is 16-bit. */ + if (ginsn_opsize_prefix_p ()) + stack_opnd_size = 2; + ginsn_next = ginsn_new_add (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_SRC_IMM, 0, stack_opnd_size, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn, ginsn_next)); + break; + + case 0x6a: /* push imm8. */ + case 0x68: /* push imm16/imm32. */ + if (i.tm.opcode_space != SPACE_BASE) + break; + /* Check if operation size is 16-bit. */ + if (ginsn_opsize_prefix_p ()) + stack_opnd_size = 2; + /* Skip getting the value of imm from machine instruction + because this is not important for SCFI. */ + ginsn = ginsn_new_sub (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_SRC_IMM, 0, stack_opnd_size, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn); + ginsn_next = ginsn_new_store (insn_end_sym, false, + GINSN_SRC_IMM, 0, + GINSN_DST_INDIRECT, REG_SP, 0); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn, ginsn_next)); + break; + + /* PS: Opcodes 0x80 ... 0x8f with opcode_space SPACE_0F are present + only after relaxation. They do not need to be handled for ginsn + creation. */ + case 0x70 ... 0x7f: + if (i.tm.opcode_space != SPACE_BASE) + break; + ginsn = x86_ginsn_jump (insn_end_sym, true); + break; + + case 0x80: + case 0x81: + case 0x83: + if (i.tm.opcode_space != SPACE_BASE) + break; + ginsn = x86_ginsn_alu_imm (insn_end_sym); + break; + + case 0x8a: /* mov r/m8, r8. */ + case 0x8b: /* mov r/m(16/32/64), r(16/32/64). */ + case 0x88: /* mov r8, r/m8. */ + case 0x89: /* mov r(16/32/64), r/m(16/32/64). */ + if (i.tm.opcode_space != SPACE_BASE) + break; + ginsn = x86_ginsn_move (insn_end_sym); + break; + + case 0x8d: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* lea disp(%base,%index,imm) %dst. */ + ginsn = x86_ginsn_lea (insn_end_sym); + break; + + case 0x8f: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* pop to reg/mem. */ + if (i.mem_operands) + { + mem_reg = (i.base_reg) ? i.base_reg : i.index_reg; + /* Use dummy register if no base or index. Unlike other opcodes, + ginsns must be generated as this affect stack pointer. */ + dw2_regnum = (mem_reg + ? ginsn_dw2_regnum (mem_reg) + : GINSN_DW2_REGNUM_RSI_DUMMY); + } + else + dw2_regnum = ginsn_dw2_regnum (i.op[0].regs); + ginsn = ginsn_new_load (insn_end_sym, false, + GINSN_SRC_INDIRECT, REG_SP, 0, + GINSN_DST_INDIRECT, dw2_regnum); + ginsn_set_where (ginsn); + /* Check if operation size is 16-bit. */ + if (ginsn_opsize_prefix_p ()) + stack_opnd_size = 2; + ginsn_next = ginsn_new_add (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_SRC_IMM, 0, stack_opnd_size, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn, ginsn_next)); + break; + + case 0x9c: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* pushf / pushfq. */ + /* Check if operation size is 16-bit. */ + if (ginsn_opsize_prefix_p ()) + stack_opnd_size = 2; + ginsn = ginsn_new_sub (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_SRC_IMM, 0, stack_opnd_size, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn); + /* FIXME - hardcode the actual DWARF reg number value. As for SCFI + correctness, although this behaves simply a placeholder value; its + just clearer if the value is correct. */ + dw2_regnum = GINSN_DW2_REGNUM_EFLAGS; + ginsn_next = ginsn_new_store (insn_end_sym, false, + GINSN_SRC_REG, dw2_regnum, + GINSN_DST_INDIRECT, REG_SP, 0); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn, ginsn_next)); + break; + + case 0x9d: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* popf / popfq. */ + /* Check if operation size is 16-bit. */ + if (ginsn_opsize_prefix_p ()) + stack_opnd_size = 2; + /* FIXME - hardcode the actual DWARF reg number value. As for SCFI + correctness, although this behaves simply a placeholder value; its + just clearer if the value is correct. */ + dw2_regnum = GINSN_DW2_REGNUM_EFLAGS; + ginsn = ginsn_new_load (insn_end_sym, false, + GINSN_SRC_INDIRECT, REG_SP, 0, + GINSN_DST_REG, dw2_regnum); + ginsn_set_where (ginsn); + ginsn_next = ginsn_new_add (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_SRC_IMM, 0, stack_opnd_size, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn, ginsn_next)); + break; + + case 0xff: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* push from reg/mem. */ + if (i.tm.extension_opcode == 6) + { + /* Check if operation size is 16-bit. */ + if (ginsn_opsize_prefix_p ()) + stack_opnd_size = 2; + ginsn = ginsn_new_sub (insn_end_sym, false, + GINSN_SRC_REG, REG_SP, 0, + GINSN_SRC_IMM, 0, stack_opnd_size, + GINSN_DST_REG, REG_SP, 0); + ginsn_set_where (ginsn); + if (i.mem_operands) + { + mem_reg = (i.base_reg) ? i.base_reg : i.index_reg; + /* Use dummy register if no base or index. Unlike other opcodes, + ginsns must be generated as this affect stack pointer. */ + dw2_regnum = (mem_reg + ? ginsn_dw2_regnum (mem_reg) + : GINSN_DW2_REGNUM_RSI_DUMMY); + } + else + dw2_regnum = ginsn_dw2_regnum (i.op[0].regs); + ginsn_next = ginsn_new_store (insn_end_sym, false, + GINSN_SRC_INDIRECT, dw2_regnum, + GINSN_DST_INDIRECT, REG_SP, 0); + ginsn_set_where (ginsn_next); + gas_assert (!ginsn_link_next (ginsn, ginsn_next)); + } + else if (i.tm.extension_opcode == 4) + { + /* jmp r/m. E.g., notrack jmp *%rax. */ + if (i.reg_operands) + { + dw2_regnum = ginsn_dw2_regnum (i.op[0].regs); + ginsn = ginsn_new_jump (insn_end_sym, true, + GINSN_SRC_REG, dw2_regnum, NULL); + ginsn_set_where (ginsn); + } + else if (i.mem_operands && i.index_reg) + { + /* jmp *0x0(,%rax,8). */ + dw2_regnum = ginsn_dw2_regnum (i.index_reg); + ginsn = ginsn_new_jump (insn_end_sym, true, + GINSN_SRC_REG, dw2_regnum, NULL); + ginsn_set_where (ginsn); + } + else if (i.mem_operands && i.base_reg) + { + dw2_regnum = ginsn_dw2_regnum (i.base_reg); + ginsn = ginsn_new_jump (insn_end_sym, true, + GINSN_SRC_REG, dw2_regnum, NULL); + ginsn_set_where (ginsn); + } + } + else if (i.tm.extension_opcode == 2) + { + /* 0xFF /2 (call). */ + if (i.reg_operands) + { + dw2_regnum = ginsn_dw2_regnum (i.op[0].regs); + ginsn = ginsn_new_call (insn_end_sym, true, + GINSN_SRC_REG, dw2_regnum, NULL); + ginsn_set_where (ginsn); + } + else if (i.mem_operands && i.base_reg) + { + dw2_regnum = ginsn_dw2_regnum (i.base_reg); + ginsn = ginsn_new_call (insn_end_sym, true, + GINSN_SRC_REG, dw2_regnum, NULL); + ginsn_set_where (ginsn); + } + } + break; + + case 0xc2: /* ret imm16. */ + case 0xc3: /* ret. */ + if (i.tm.opcode_space != SPACE_BASE) + break; + /* Near ret. */ + ginsn = ginsn_new_return (insn_end_sym, true); + ginsn_set_where (ginsn); + break; + + case 0xc8: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* enter. */ + ginsn = x86_ginsn_enter (insn_end_sym); + break; + + case 0xc9: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* leave. */ + ginsn = x86_ginsn_leave (insn_end_sym); + break; + + case 0xe0 ... 0xe2: /* loop / loope / loopne. */ + case 0xe3: /* jecxz / jrcxz. */ + if (i.tm.opcode_space != SPACE_BASE) + break; + ginsn = x86_ginsn_jump (insn_end_sym, true); + ginsn_set_where (ginsn); + break; + + case 0xe8: + if (i.tm.opcode_space != SPACE_BASE) + break; + /* PS: SCFI machinery does not care about which func is being + called. OK to skip that info. */ + ginsn = ginsn_new_call (insn_end_sym, true, + GINSN_SRC_SYMBOL, 0, NULL); + ginsn_set_where (ginsn); + break; + + /* PS: opcode 0xe9 appears only after relaxation. Skip here. */ + case 0xeb: + /* If opcode_space != SPACE_BASE, this is not a jmp insn. Skip it + for GINSN_GEN_SCFI. */ + if (i.tm.opcode_space != SPACE_BASE) + break; + /* Unconditional jmp. */ + ginsn = x86_ginsn_jump (insn_end_sym, false); + ginsn_set_where (ginsn); + break; + + default: + /* TBD_GINSN_GEN_NOT_SCFI: Skip all other opcodes uninteresting for + GINSN_GEN_SCFI mode. */ + break; + } + + if (!ginsn && !x86_ginsn_safe_to_skip_p ()) + { + /* For all unhandled insns that are not whitelisted, check that they do + not impact SCFI correctness. */ + err = x86_ginsn_unhandled (); + switch (err) + { + case X86_GINSN_UNHANDLED_NONE: + break; + case X86_GINSN_UNHANDLED_DEST_REG: + /* Not all writes to REG_FP are harmful in context of SCFI. Simply + generate a GINSN_TYPE_OTHER with destination set to the + appropriate register. The SCFI machinery will bail out if this + ginsn affects SCFI correctness. */ + dw2_regnum = ginsn_dw2_regnum (i.op[i.operands - 1].regs); + ginsn = ginsn_new_other (insn_end_sym, true, + GINSN_SRC_IMM, 0, + GINSN_SRC_IMM, 0, + GINSN_DST_REG, dw2_regnum); + ginsn_set_where (ginsn); + break; + case X86_GINSN_UNHANDLED_CFG: + case X86_GINSN_UNHANDLED_STACKOP: + as_bad (_("SCFI: unhandled op %#x may cause incorrect CFI"), opcode); + break; + case X86_GINSN_UNHANDLED_UNEXPECTED: + as_bad (_("SCFI: unexpected op %#x may cause incorrect CFI"), + opcode); + break; + default: + abort (); + break; + } + } + + return ginsn; +} + +#endif + /* This is the guts of the machine-dependent assembler. LINE points to a machine dependent instruction. This function is supposed to emit the frags/bytes it assembles to. */ @@ -5870,6 +6960,17 @@ md_assemble (char *line) /* We are ready to output the insn. */ output_insn (last_insn); +#if defined (OBJ_MAYBE_ELF) || defined (OBJ_ELF) + /* PS: SCFI is enabled only for System V AMD64 ABI. The ABI check has been + performed in i386_target_format. */ + if (IS_ELF && flag_synth_cfi) + { + ginsnS *ginsn; + ginsn = x86_ginsn_new (symbol_temp_new_now (), frch_ginsn_gen_mode ()); + frch_ginsn_data_append (ginsn); + } +#endif + insert_lfence_after (); if (i.tm.opcode_modifier.isprefix) @@ -12144,6 +13245,13 @@ s_insn (int dummy ATTRIBUTE_UNUSED) last_insn->name = ".insn directive"; last_insn->file = as_where (&last_insn->line); +#if defined (OBJ_MAYBE_ELF) || defined (OBJ_ELF) + /* PS: SCFI is enabled only for System V AMD64 ABI. The ABI check has been + performed in i386_target_format. */ + if (IS_ELF && flag_synth_cfi) + as_bad (_("SCFI: hand-crafting instructions not supported")); +#endif + done: *saved_ilp = saved_char; input_line_pointer = line; @@ -15788,6 +16896,11 @@ i386_target_format (void) else as_fatal (_("unknown architecture")); +#if defined (OBJ_ELF) || defined (OBJ_MAYBE_ELF) + if (IS_ELF && flag_synth_cfi && x86_elf_abi != X86_64_ABI) + as_fatal (_("SCFI is not supported for this ABI")); +#endif + if (cpu_flags_all_zero (&cpu_arch_isa_flags)) cpu_arch_isa_flags = cpu_arch[flag_code == CODE_64BIT].enable; diff --git a/gas/config/tc-i386.h b/gas/config/tc-i386.h index 2499aa6..b93799a 100644 --- a/gas/config/tc-i386.h +++ b/gas/config/tc-i386.h @@ -415,6 +415,27 @@ extern bfd_vma x86_64_section_letter (int, const char **); extern void x86_cleanup (void); #define md_cleanup() x86_cleanup () +#define TARGET_USE_GINSN 1 +/* Allow GAS to synthesize DWARF CFI for hand-written asm. + PS: TARGET_USE_CFIPOP is a pre-condition. */ +#define TARGET_USE_SCFI 1 +/* Identify the maximum DWARF register number of all the registers being + tracked for SCFI. This is the last DWARF register number of the set + of SP, BP, and all callee-saved registers. For AMD64, this means + R15 (15). Use SCFI_CALLEE_SAVED_REG_P to identify which registers + are callee-saved from this set. */ +#define SCFI_MAX_REG_ID 15 +/* Identify the DWARF register number of the frame-pointer register. */ +#define REG_FP 6 +/* Identify the DWARF register number of the stack-pointer register. */ +#define REG_SP 7 +/* Some ABIs, like AMD64, use stack for call instruction. For such an ABI, + identify the initial (CFA) offset from RSP at the entry of function. */ +#define SCFI_INIT_CFA_OFFSET 8 + +#define SCFI_CALLEE_SAVED_REG_P(dw2reg) x86_scfi_callee_saved_p (dw2reg) +extern bool x86_scfi_callee_saved_p (uint32_t dw2reg_num); + /* Whether SFrame stack trace info is supported. */ extern bool x86_support_sframe_p (void); #define support_sframe_p x86_support_sframe_p -- cgit v1.1