mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-12-21 04:42:53 +08:00
c7defc5386
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.
273 lines
6.6 KiB
C
273 lines
6.6 KiB
C
/* scfidw2gen.c - Support for emission of synthesized Dwarf2 CFI.
|
|
Copyright (C) 2023 Free Software Foundation, Inc.
|
|
|
|
This file is part of GAS, the GNU Assembler.
|
|
|
|
GAS is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3, or (at your option)
|
|
any later version.
|
|
|
|
GAS is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GAS; see the file COPYING. If not, write to the Free
|
|
Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
|
|
02110-1301, USA. */
|
|
|
|
#include "as.h"
|
|
#include "ginsn.h"
|
|
#include "scfi.h"
|
|
#include "dw2gencfi.h"
|
|
#include "subsegs.h"
|
|
#include "scfidw2gen.h"
|
|
|
|
#if defined (TARGET_USE_SCFI) && defined (TARGET_USE_GINSN)
|
|
|
|
static bool scfi_ignore_warn_once;
|
|
|
|
static void
|
|
dot_scfi_ignore (int ignored ATTRIBUTE_UNUSED)
|
|
{
|
|
gas_assert (flag_synth_cfi);
|
|
|
|
if (!scfi_ignore_warn_once)
|
|
{
|
|
as_warn (_("SCFI ignores most user-specified CFI directives"));
|
|
scfi_ignore_warn_once = true;
|
|
}
|
|
ignore_rest_of_line ();
|
|
}
|
|
|
|
static void
|
|
scfi_process_cfi_label (void)
|
|
{
|
|
char *name;
|
|
ginsnS *ginsn;
|
|
|
|
name = read_symbol_name ();
|
|
if (name == NULL)
|
|
return;
|
|
|
|
/* Add a new ginsn. */
|
|
ginsn = ginsn_new_phantom (symbol_temp_new_now ());
|
|
frch_ginsn_data_append (ginsn);
|
|
|
|
scfi_op_add_cfi_label (ginsn, name);
|
|
/* TODO. */
|
|
// free (name);
|
|
|
|
demand_empty_rest_of_line ();
|
|
}
|
|
|
|
static void
|
|
scfi_process_cfi_signal_frame (void)
|
|
{
|
|
ginsnS *ginsn;
|
|
|
|
ginsn = ginsn_new_phantom (symbol_temp_new_now ());
|
|
frch_ginsn_data_append (ginsn);
|
|
|
|
scfi_op_add_signal_frame (ginsn);
|
|
}
|
|
|
|
static void
|
|
dot_scfi (int arg)
|
|
{
|
|
switch (arg)
|
|
{
|
|
case CFI_label:
|
|
scfi_process_cfi_label ();
|
|
break;
|
|
case CFI_signal_frame:
|
|
scfi_process_cfi_signal_frame ();
|
|
break;
|
|
default:
|
|
abort ();
|
|
}
|
|
}
|
|
|
|
const pseudo_typeS scfi_pseudo_table[] =
|
|
{
|
|
{ "cfi_sections", dot_cfi_sections, 0 }, /* No ignore. */
|
|
{ "cfi_signal_frame", dot_scfi, CFI_signal_frame }, /* No ignore. */
|
|
{ "cfi_label", dot_scfi, CFI_label }, /* No ignore. */
|
|
{ "cfi_startproc", dot_scfi_ignore, 0 },
|
|
{ "cfi_endproc", dot_scfi_ignore, 0 },
|
|
{ "cfi_fde_data", dot_scfi_ignore, 0 },
|
|
{ "cfi_def_cfa", dot_scfi_ignore, 0 },
|
|
{ "cfi_def_cfa_register", dot_scfi_ignore, 0 },
|
|
{ "cfi_def_cfa_offset", dot_scfi_ignore, 0 },
|
|
{ "cfi_adjust_cfa_offset", dot_scfi_ignore, 0 },
|
|
{ "cfi_offset", dot_scfi_ignore, 0 },
|
|
{ "cfi_rel_offset", dot_scfi_ignore, 0 },
|
|
{ "cfi_register", dot_scfi_ignore, 0 },
|
|
{ "cfi_return_column", dot_scfi_ignore, 0 },
|
|
{ "cfi_restore", dot_scfi_ignore, 0 },
|
|
{ "cfi_undefined", dot_scfi_ignore, 0 },
|
|
{ "cfi_same_value", dot_scfi_ignore, 0 },
|
|
{ "cfi_remember_state", dot_scfi_ignore, 0 },
|
|
{ "cfi_restore_state", dot_scfi_ignore, 0 },
|
|
{ "cfi_window_save", dot_scfi_ignore, 0 },
|
|
{ "cfi_negate_ra_state", dot_scfi_ignore, 0 },
|
|
{ "cfi_escape", dot_scfi_ignore, 0 },
|
|
{ "cfi_personality", dot_scfi_ignore, 0 },
|
|
{ "cfi_personality_id", dot_scfi_ignore, 0 },
|
|
{ "cfi_lsda", dot_scfi_ignore, 0 },
|
|
{ "cfi_val_encoded_addr", dot_scfi_ignore, 0 },
|
|
{ "cfi_inline_lsda", dot_scfi_ignore, 0 },
|
|
{ "cfi_val_offset", dot_scfi_ignore, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
void
|
|
scfi_dot_cfi_startproc (const symbolS *start_sym)
|
|
{
|
|
if (frchain_now->frch_cfi_data != NULL)
|
|
{
|
|
as_bad (_("SCFI: missing previous SCFI endproc marker"));
|
|
return;
|
|
}
|
|
|
|
cfi_new_fde ((symbolS *)start_sym);
|
|
|
|
cfi_set_sections ();
|
|
|
|
frchain_now->frch_cfi_data->cur_cfa_offset = 0;
|
|
|
|
/* By default, SCFI machinery assumes .cfi_startproc is used without
|
|
parameter simple. */
|
|
tc_cfi_frame_initial_instructions ();
|
|
|
|
if ((all_cfi_sections & CFI_EMIT_target) != 0)
|
|
tc_cfi_startproc ();
|
|
}
|
|
|
|
void
|
|
scfi_dot_cfi_endproc (const symbolS *end_sym)
|
|
{
|
|
struct fde_entry *fde_last;
|
|
|
|
if (frchain_now->frch_cfi_data == NULL)
|
|
{
|
|
as_bad (_(".cfi_endproc without corresponding .cfi_startproc"));
|
|
return;
|
|
}
|
|
|
|
fde_last = frchain_now->frch_cfi_data->cur_fde_data;
|
|
cfi_set_last_fde (fde_last);
|
|
|
|
cfi_end_fde ((symbolS *)end_sym);
|
|
|
|
if ((all_cfi_sections & CFI_EMIT_target) != 0)
|
|
tc_cfi_endproc (fde_last);
|
|
}
|
|
|
|
void
|
|
scfi_dot_cfi (int arg, unsigned reg1, unsigned reg2, offsetT offset,
|
|
const char *name, const symbolS *advloc)
|
|
{
|
|
if (frchain_now->frch_cfi_data == NULL)
|
|
{
|
|
as_bad (_("CFI instruction used without previous .cfi_startproc"));
|
|
return;
|
|
}
|
|
|
|
/* If the last address was not at the current PC, advance to current. */
|
|
if (frchain_now->frch_cfi_data->last_address != advloc)
|
|
cfi_add_advance_loc ((symbolS *)advloc);
|
|
|
|
switch (arg)
|
|
{
|
|
case DW_CFA_offset:
|
|
cfi_add_CFA_offset (reg1, offset);
|
|
break;
|
|
|
|
case DW_CFA_val_offset:
|
|
cfi_add_CFA_val_offset (reg1, offset);
|
|
break;
|
|
|
|
case CFI_rel_offset:
|
|
cfi_add_CFA_offset (reg1,
|
|
offset - frchain_now->frch_cfi_data->cur_cfa_offset);
|
|
break;
|
|
|
|
case DW_CFA_def_cfa:
|
|
cfi_add_CFA_def_cfa (reg1, offset);
|
|
break;
|
|
|
|
case DW_CFA_register:
|
|
cfi_add_CFA_register (reg1, reg2);
|
|
break;
|
|
|
|
case DW_CFA_def_cfa_register:
|
|
cfi_add_CFA_def_cfa_register (reg1);
|
|
break;
|
|
|
|
case DW_CFA_def_cfa_offset:
|
|
cfi_add_CFA_def_cfa_offset (offset);
|
|
break;
|
|
|
|
case CFI_adjust_cfa_offset:
|
|
cfi_add_CFA_def_cfa_offset (frchain_now->frch_cfi_data->cur_cfa_offset
|
|
+ offset);
|
|
break;
|
|
|
|
case DW_CFA_restore:
|
|
cfi_add_CFA_restore (reg1);
|
|
break;
|
|
|
|
case DW_CFA_remember_state:
|
|
cfi_add_CFA_remember_state ();
|
|
break;
|
|
|
|
case DW_CFA_restore_state:
|
|
cfi_add_CFA_restore_state ();
|
|
break;
|
|
|
|
case CFI_label:
|
|
cfi_add_label (name);
|
|
break;
|
|
|
|
case CFI_signal_frame:
|
|
frchain_now->frch_cfi_data->cur_fde_data->signal_frame = 1;
|
|
break;
|
|
|
|
/*
|
|
case DW_CFA_undefined:
|
|
for (;;)
|
|
{
|
|
reg1 = cfi_parse_reg ();
|
|
cfi_add_CFA_undefined (reg1);
|
|
SKIP_WHITESPACE ();
|
|
if (*input_line_pointer != ',')
|
|
break;
|
|
++input_line_pointer;
|
|
}
|
|
break;
|
|
|
|
case DW_CFA_same_value:
|
|
reg1 = cfi_parse_reg ();
|
|
cfi_add_CFA_same_value (reg1);
|
|
break;
|
|
|
|
case CFI_return_column:
|
|
reg1 = cfi_parse_reg ();
|
|
cfi_set_return_column (reg1);
|
|
break;
|
|
|
|
case DW_CFA_GNU_window_save:
|
|
cfi_add_CFA_insn (DW_CFA_GNU_window_save);
|
|
break;
|
|
|
|
*/
|
|
default:
|
|
abort ();
|
|
}
|
|
}
|
|
|
|
#endif
|