mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-21 01:12:32 +08:00
af4eb9e20a
When the SCFI machinery detects that a register has been restored from stack, it makes some state changes in the SCFI state object. Prior to the patch, scfi_state_restore_reg () was setting a value of (reg, CFI_IN_REG) for (base, state) respectively. This was causing issues in the cmp_scfi_state () function: - The default state of all (callee-saved) regs at the beginning of function is set to (0, CFI_UNDEFINED). - If a register is saved and restored on some control path, the state of reg is (reg, CFI_IN_REG) on that path. - On another control path where the register was perhaps not used (or saved/restored on stack) remains (0, CFI_UNDEFINED). - The two states should be treated equal, however, at the point in program after the register has been restored. Fix this by resetting the state to (0, CFI_UNDEFINED) in scfi_state_restore_reg (). A testcase (scfi-cfg-4.s) for this is added in a subsequent commit. gas/ * scfi.c (scfi_state_restore_reg): Reset to 0, CFI_UNDEFINED for base, state.
1242 lines
34 KiB
C
1242 lines
34 KiB
C
/* scfi.c - Support for synthesizing DWARF CFI for hand-written asm.
|
|
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 "scfi.h"
|
|
#include "subsegs.h"
|
|
#include "scfidw2gen.h"
|
|
#include "dw2gencfi.h"
|
|
|
|
#if defined (TARGET_USE_SCFI) && defined (TARGET_USE_GINSN)
|
|
|
|
/* Beyond the target defined number of registers to be tracked
|
|
(SCFI_MAX_REG_ID), keep the next register ID, in sequence, for REG_CFA. */
|
|
#define REG_CFA (SCFI_MAX_REG_ID+1)
|
|
/* Define the total number of registers being tracked.
|
|
Used as index into an array of cfi_reglocS. Note that a ginsn may carry a
|
|
register number greater than MAX_NUM_SCFI_REGS, e.g., for the ginsns
|
|
corresponding to push fs/gs in AMD64. */
|
|
#define MAX_NUM_SCFI_REGS (REG_CFA+1)
|
|
|
|
#define REG_INVALID ((unsigned int)-1)
|
|
|
|
enum cfi_reglocstate
|
|
{
|
|
CFI_UNDEFINED,
|
|
CFI_IN_REG,
|
|
CFI_ON_STACK
|
|
};
|
|
|
|
/* Location at which CFI register is saved.
|
|
|
|
A CFI register (callee-saved registers, RA/LR) are always an offset from
|
|
the CFA. REG_CFA itself, however, may have REG_SP or REG_FP as base
|
|
register. Hence, keep the base reg ID and offset per tracked register. */
|
|
|
|
struct cfi_regloc
|
|
{
|
|
/* Base reg ID (DWARF register number). */
|
|
unsigned int base;
|
|
/* Location as offset from the CFA. */
|
|
offsetT offset;
|
|
/* Current state of the CFI register. */
|
|
enum cfi_reglocstate state;
|
|
};
|
|
|
|
typedef struct cfi_regloc cfi_reglocS;
|
|
|
|
struct scfi_op_data
|
|
{
|
|
const char *name;
|
|
};
|
|
|
|
typedef struct scfi_op_data scfi_op_dataS;
|
|
|
|
/* SCFI operation.
|
|
|
|
An SCFI operation represents a single atomic change to the SCFI state.
|
|
This can also be understood as an abstraction for what eventually gets
|
|
emitted as a DWARF CFI operation. */
|
|
|
|
struct scfi_op
|
|
{
|
|
/* An SCFI op updates the state of either the CFA or other tracked
|
|
(callee-saved, REG_SP etc) registers. 'reg' is in the DWARF register
|
|
number space and must be strictly less than MAX_NUM_SCFI_REGS. */
|
|
unsigned int reg;
|
|
/* Location of the reg. */
|
|
cfi_reglocS loc;
|
|
/* DWARF CFI opcode. */
|
|
uint32_t dw2cfi_op;
|
|
/* Some SCFI ops, e.g., for CFI_label, may need to carry additional data. */
|
|
scfi_op_dataS *op_data;
|
|
/* A linked list. */
|
|
struct scfi_op *next;
|
|
};
|
|
|
|
/* SCFI State - accumulated unwind information at a PC.
|
|
|
|
SCFI state is the accumulated unwind information encompassing:
|
|
- REG_SP, REG_FP,
|
|
- RA, and
|
|
- all callee-saved registers.
|
|
|
|
Note that SCFI_MAX_REG_ID is target/ABI dependent and is provided by the
|
|
backends. The backend must also identify the DWARF register numbers for
|
|
the REG_SP, and REG_FP registers. */
|
|
|
|
struct scfi_state
|
|
{
|
|
cfi_reglocS regs[MAX_NUM_SCFI_REGS];
|
|
cfi_reglocS scratch[MAX_NUM_SCFI_REGS];
|
|
/* Current stack size. */
|
|
offsetT stack_size;
|
|
/* Whether the stack size is known.
|
|
Stack size may become untraceable depending on the specific stack
|
|
manipulation machine instruction, e.g., rsp = rsp op reg instruction
|
|
makes the stack size untraceable. */
|
|
bool traceable_p;
|
|
};
|
|
|
|
/* Initialize a new SCFI op. */
|
|
|
|
static scfi_opS *
|
|
init_scfi_op (void)
|
|
{
|
|
scfi_opS *op = XCNEW (scfi_opS);
|
|
|
|
return op;
|
|
}
|
|
|
|
/* Free the SCFI ops, given the HEAD of the list. */
|
|
|
|
void
|
|
scfi_ops_cleanup (scfi_opS **head)
|
|
{
|
|
scfi_opS *op;
|
|
scfi_opS *next;
|
|
|
|
if (!head || !*head)
|
|
return;
|
|
|
|
op = *head;
|
|
next = op->next;
|
|
|
|
while (op)
|
|
{
|
|
free (op->op_data);
|
|
free (op);
|
|
op = next;
|
|
next = op ? op->next : NULL;
|
|
}
|
|
|
|
free (head);
|
|
}
|
|
|
|
/* Compare two SCFI states. */
|
|
|
|
static int
|
|
cmp_scfi_state (scfi_stateS *state1, scfi_stateS *state2)
|
|
{
|
|
int ret;
|
|
|
|
if (!state1 || !state2)
|
|
return 1;
|
|
|
|
/* Skip comparing the scratch[] value of registers. The user visible
|
|
unwind information is derived from the regs[] from the SCFI state. */
|
|
ret = memcmp (state1->regs, state2->regs,
|
|
sizeof (cfi_reglocS) * MAX_NUM_SCFI_REGS);
|
|
|
|
/* For user functions which perform dynamic stack allocation, after switching
|
|
t REG_FP based CFA tracking, it is perfectly possible to have stack usage
|
|
in some control flows. Further, the different control flows may even not
|
|
have the same idea of CFA tracking (likely later necessitating generation
|
|
of .cfi_remember_state / .cfi_restore_state pair). */
|
|
ret |= state1->regs[REG_CFA].base != state2->regs[REG_CFA].base;
|
|
|
|
if (!ret && state1->regs[REG_CFA].base == REG_SP)
|
|
ret |= state1->stack_size != state2->stack_size;
|
|
|
|
ret |= state1->traceable_p != state2->traceable_p;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
scfi_state_update_reg (scfi_stateS *state, uint32_t dst, uint32_t base,
|
|
int32_t offset)
|
|
{
|
|
if (dst >= MAX_NUM_SCFI_REGS)
|
|
return;
|
|
|
|
state->regs[dst].base = base;
|
|
state->regs[dst].offset = offset;
|
|
}
|
|
#endif
|
|
|
|
/* Update the SCFI state of REG as available on execution stack at OFFSET
|
|
from REG_CFA (BASE).
|
|
|
|
Note that BASE must be REG_CFA, because any other base (REG_SP, REG_FP)
|
|
is by definition transitory in the function. */
|
|
|
|
static void
|
|
scfi_state_save_reg (scfi_stateS *state, unsigned int reg, unsigned int base,
|
|
offsetT offset)
|
|
{
|
|
if (reg >= MAX_NUM_SCFI_REGS)
|
|
return;
|
|
|
|
gas_assert (base == REG_CFA);
|
|
|
|
state->regs[reg].base = base;
|
|
state->regs[reg].offset = offset;
|
|
state->regs[reg].state = CFI_ON_STACK;
|
|
}
|
|
|
|
static void
|
|
scfi_state_restore_reg (scfi_stateS *state, unsigned int reg)
|
|
{
|
|
if (reg >= MAX_NUM_SCFI_REGS)
|
|
return;
|
|
|
|
/* Sanity check. See Rule 4. */
|
|
gas_assert (state->regs[reg].state == CFI_ON_STACK);
|
|
gas_assert (state->regs[reg].base == REG_CFA);
|
|
|
|
/* PS: the register may still be on stack much after the restore. Reset the
|
|
SCFI state to CFI_UNDEFINED, however, to indicate that the most updated
|
|
source of value is register itself from here onwards. */
|
|
state->regs[reg].base = 0;
|
|
state->regs[reg].offset = 0;
|
|
state->regs[reg].state = CFI_UNDEFINED;
|
|
}
|
|
|
|
/* Identify if the given GAS instruction GINSN saves a register
|
|
(of interest) on stack. */
|
|
|
|
static bool
|
|
ginsn_scfi_save_reg_p (ginsnS *ginsn, scfi_stateS *state)
|
|
{
|
|
bool save_reg_p = false;
|
|
struct ginsn_src *src;
|
|
struct ginsn_dst *dst;
|
|
|
|
src = ginsn_get_src1 (ginsn);
|
|
dst = ginsn_get_dst (ginsn);
|
|
|
|
/* The first save to stack of callee-saved register is deemed as
|
|
register save. */
|
|
if (!ginsn_track_reg_p (ginsn_get_src_reg (src), GINSN_GEN_SCFI)
|
|
|| state->regs[ginsn_get_src_reg (src)].state == CFI_ON_STACK)
|
|
return save_reg_p;
|
|
|
|
/* A register save insn may be an indirect mov. */
|
|
if (ginsn->type == GINSN_TYPE_MOV
|
|
&& ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT
|
|
&& (ginsn_get_dst_reg (dst) == REG_SP
|
|
|| (ginsn_get_dst_reg (dst) == REG_FP
|
|
&& state->regs[REG_CFA].base == REG_FP)))
|
|
save_reg_p = true;
|
|
/* or an explicit store to stack. */
|
|
else if (ginsn->type == GINSN_TYPE_STORE
|
|
&& ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT
|
|
&& ginsn_get_dst_reg (dst) == REG_SP)
|
|
save_reg_p = true;
|
|
|
|
return save_reg_p;
|
|
}
|
|
|
|
/* Identify if the given GAS instruction GINSN restores a register
|
|
(of interest) on stack. */
|
|
|
|
static bool
|
|
ginsn_scfi_restore_reg_p (ginsnS *ginsn, scfi_stateS *state)
|
|
{
|
|
bool restore_reg_p = false;
|
|
struct ginsn_dst *dst;
|
|
struct ginsn_src *src1;
|
|
|
|
dst = ginsn_get_dst (ginsn);
|
|
src1 = ginsn_get_src1 (ginsn);
|
|
|
|
if (!ginsn_track_reg_p (ginsn_get_dst_reg (dst), GINSN_GEN_SCFI))
|
|
return restore_reg_p;
|
|
|
|
/* A register restore insn may be an indirect mov... */
|
|
if (ginsn->type == GINSN_TYPE_MOV
|
|
&& ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT
|
|
&& (ginsn_get_src_reg (src1) == REG_SP
|
|
|| (ginsn_get_src_reg (src1) == REG_FP
|
|
&& state->regs[REG_CFA].base == REG_FP)))
|
|
restore_reg_p = true;
|
|
/* ...or an explicit load from stack. */
|
|
else if (ginsn->type == GINSN_TYPE_LOAD
|
|
&& ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT
|
|
&& ginsn_get_src_reg (src1) == REG_SP)
|
|
restore_reg_p = true;
|
|
|
|
return restore_reg_p;
|
|
}
|
|
|
|
/* Append the SCFI operation OP to the list of SCFI operations in the
|
|
given GINSN. */
|
|
|
|
static int
|
|
ginsn_append_scfi_op (ginsnS *ginsn, scfi_opS *op)
|
|
{
|
|
scfi_opS *sop;
|
|
|
|
if (!ginsn || !op)
|
|
return 1;
|
|
|
|
if (!ginsn->scfi_ops)
|
|
{
|
|
ginsn->scfi_ops = XCNEW (scfi_opS *);
|
|
*ginsn->scfi_ops = op;
|
|
}
|
|
else
|
|
{
|
|
/* Add to tail. Most ginsns have a single SCFI operation,
|
|
so this traversal for every insertion is acceptable for now. */
|
|
sop = *ginsn->scfi_ops;
|
|
while (sop->next)
|
|
sop = sop->next;
|
|
|
|
sop->next = op;
|
|
}
|
|
ginsn->num_scfi_ops++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
scfi_op_add_def_cfa_reg (scfi_stateS *state, ginsnS *ginsn, unsigned int reg)
|
|
{
|
|
scfi_opS *op = NULL;
|
|
|
|
state->regs[REG_CFA].base = reg;
|
|
|
|
op = init_scfi_op ();
|
|
|
|
op->dw2cfi_op = DW_CFA_def_cfa_register;
|
|
op->reg = REG_CFA;
|
|
op->loc = state->regs[REG_CFA];
|
|
|
|
ginsn_append_scfi_op (ginsn, op);
|
|
}
|
|
|
|
static void
|
|
scfi_op_add_cfa_offset_inc (scfi_stateS *state, ginsnS *ginsn, offsetT num)
|
|
{
|
|
scfi_opS *op = NULL;
|
|
|
|
state->regs[REG_CFA].offset -= num;
|
|
|
|
op = init_scfi_op ();
|
|
|
|
op->dw2cfi_op = DW_CFA_def_cfa_offset;
|
|
op->reg = REG_CFA;
|
|
op->loc = state->regs[REG_CFA];
|
|
|
|
ginsn_append_scfi_op (ginsn, op);
|
|
}
|
|
|
|
static void
|
|
scfi_op_add_cfa_offset_dec (scfi_stateS *state, ginsnS *ginsn, offsetT num)
|
|
{
|
|
scfi_opS *op = NULL;
|
|
|
|
state->regs[REG_CFA].offset += num;
|
|
|
|
op = init_scfi_op ();
|
|
|
|
op->dw2cfi_op = DW_CFA_def_cfa_offset;
|
|
op->reg = REG_CFA;
|
|
op->loc = state->regs[REG_CFA];
|
|
|
|
ginsn_append_scfi_op (ginsn, op);
|
|
}
|
|
|
|
static void
|
|
scfi_op_add_def_cfa (scfi_stateS *state, ginsnS *ginsn, unsigned int reg,
|
|
offsetT num)
|
|
{
|
|
scfi_opS *op = NULL;
|
|
|
|
state->regs[REG_CFA].base = reg;
|
|
state->regs[REG_CFA].offset = num;
|
|
|
|
op = init_scfi_op ();
|
|
|
|
op->dw2cfi_op = DW_CFA_def_cfa;
|
|
op->reg = REG_CFA;
|
|
op->loc = state->regs[REG_CFA];
|
|
|
|
ginsn_append_scfi_op (ginsn, op);
|
|
}
|
|
|
|
static void
|
|
scfi_op_add_cfi_offset (scfi_stateS *state, ginsnS *ginsn, unsigned int reg)
|
|
{
|
|
scfi_opS *op = NULL;
|
|
|
|
op = init_scfi_op ();
|
|
|
|
op->dw2cfi_op = DW_CFA_offset;
|
|
op->reg = reg;
|
|
op->loc = state->regs[reg];
|
|
|
|
ginsn_append_scfi_op (ginsn, op);
|
|
}
|
|
|
|
static void
|
|
scfi_op_add_cfa_restore (ginsnS *ginsn, unsigned int reg)
|
|
{
|
|
scfi_opS *op = NULL;
|
|
|
|
op = init_scfi_op ();
|
|
|
|
op->dw2cfi_op = DW_CFA_restore;
|
|
op->reg = reg;
|
|
op->loc.base = REG_INVALID;
|
|
op->loc.offset = 0;
|
|
|
|
ginsn_append_scfi_op (ginsn, op);
|
|
}
|
|
|
|
static void
|
|
scfi_op_add_cfi_remember_state (ginsnS *ginsn)
|
|
{
|
|
scfi_opS *op = NULL;
|
|
|
|
op = init_scfi_op ();
|
|
|
|
op->dw2cfi_op = DW_CFA_remember_state;
|
|
|
|
ginsn_append_scfi_op (ginsn, op);
|
|
}
|
|
|
|
static void
|
|
scfi_op_add_cfi_restore_state (ginsnS *ginsn)
|
|
{
|
|
scfi_opS *op = NULL;
|
|
|
|
op = init_scfi_op ();
|
|
|
|
op->dw2cfi_op = DW_CFA_restore_state;
|
|
|
|
/* FIXME - add to the beginning of the scfi_ops. */
|
|
ginsn_append_scfi_op (ginsn, op);
|
|
}
|
|
|
|
void
|
|
scfi_op_add_cfi_label (ginsnS *ginsn, const char *name)
|
|
{
|
|
scfi_opS *op = NULL;
|
|
|
|
op = init_scfi_op ();
|
|
op->dw2cfi_op = CFI_label;
|
|
op->op_data = XCNEW (scfi_op_dataS);
|
|
op->op_data->name = name;
|
|
|
|
ginsn_append_scfi_op (ginsn, op);
|
|
}
|
|
|
|
void
|
|
scfi_op_add_signal_frame (ginsnS *ginsn)
|
|
{
|
|
scfi_opS *op = NULL;
|
|
|
|
op = init_scfi_op ();
|
|
op->dw2cfi_op = CFI_signal_frame;
|
|
|
|
ginsn_append_scfi_op (ginsn, op);
|
|
}
|
|
|
|
static int
|
|
verify_heuristic_traceable_reg_fp (ginsnS *ginsn, scfi_stateS *state)
|
|
{
|
|
/* The function uses this variable to issue error to user right away. */
|
|
int fp_traceable_p = 0;
|
|
struct ginsn_dst *dst;
|
|
struct ginsn_src *src1;
|
|
struct ginsn_src *src2;
|
|
|
|
src1 = ginsn_get_src1 (ginsn);
|
|
src2 = ginsn_get_src2 (ginsn);
|
|
dst = ginsn_get_dst (ginsn);
|
|
|
|
/* Stack manipulation can be done in a variety of ways. A program may
|
|
allocate stack statically or may perform dynamic stack allocation in
|
|
the prologue.
|
|
|
|
The SCFI machinery in GAS is based on some heuristics:
|
|
|
|
- Rule 3 If the base register for CFA tracking is REG_FP, the program
|
|
must not clobber REG_FP, unless it is for switch to REG_SP based CFA
|
|
tracking (via say, a pop %rbp in X86). */
|
|
|
|
/* Check all applicable instructions with dest REG_FP, when the CFA base
|
|
register is REG_FP. */
|
|
if (state->regs[REG_CFA].base == REG_FP && ginsn_get_dst_reg (dst) == REG_FP)
|
|
{
|
|
/* Excuse the add/sub with imm usage: They are OK. */
|
|
if ((ginsn->type == GINSN_TYPE_ADD || ginsn->type == GINSN_TYPE_SUB)
|
|
&& ginsn_get_src_reg (src1) == REG_FP
|
|
&& ginsn_get_src_type (src2) == GINSN_SRC_IMM)
|
|
fp_traceable_p = 0;
|
|
/* REG_FP restore is OK too. */
|
|
else if (ginsn->type == GINSN_TYPE_LOAD)
|
|
fp_traceable_p = 0;
|
|
/* mov's to memory with REG_FP base do not make REG_FP untraceable. */
|
|
else if (ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT
|
|
&& (ginsn->type == GINSN_TYPE_MOV
|
|
|| ginsn->type == GINSN_TYPE_STORE))
|
|
fp_traceable_p = 0;
|
|
/* Manipulations of the values possibly on stack are OK too. */
|
|
else if ((ginsn->type == GINSN_TYPE_ADD || ginsn->type == GINSN_TYPE_SUB
|
|
|| ginsn->type == GINSN_TYPE_AND)
|
|
&& ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT)
|
|
fp_traceable_p = 0;
|
|
/* All other ginsns with REG_FP as destination make REG_FP not
|
|
traceable. */
|
|
else
|
|
fp_traceable_p = 1;
|
|
}
|
|
|
|
if (fp_traceable_p)
|
|
as_bad_where (ginsn->file, ginsn->line,
|
|
_("SCFI: usage of REG_FP as scratch not supported"));
|
|
|
|
return fp_traceable_p;
|
|
}
|
|
|
|
static int
|
|
verify_heuristic_traceable_stack_manipulation (ginsnS *ginsn,
|
|
scfi_stateS *state)
|
|
{
|
|
/* The function uses this variable to issue error to user right away. */
|
|
int sp_untraceable_p = 0;
|
|
bool possibly_untraceable = false;
|
|
struct ginsn_dst *dst;
|
|
struct ginsn_src *src1;
|
|
struct ginsn_src *src2;
|
|
|
|
src1 = ginsn_get_src1 (ginsn);
|
|
src2 = ginsn_get_src2 (ginsn);
|
|
dst = ginsn_get_dst (ginsn);
|
|
|
|
/* Stack manipulation can be done in a variety of ways. A program may
|
|
allocate stack statically in prologue or may need to do dynamic stack
|
|
allocation.
|
|
|
|
The SCFI machinery in GAS is based on some heuristics:
|
|
|
|
- Rule 1 The base register for CFA tracking may be either REG_SP or
|
|
REG_FP.
|
|
|
|
- Rule 2 If the base register for CFA tracking is REG_SP, the precise
|
|
amount of stack usage (and hence, the value of rsp) must be known at
|
|
all times. */
|
|
|
|
if (ginsn->type == GINSN_TYPE_MOV
|
|
&& ginsn_get_dst_type (dst) == GINSN_DST_REG
|
|
&& ginsn_get_dst_reg (dst) == REG_SP
|
|
&& ginsn_get_src_type (src1) == GINSN_SRC_REG
|
|
/* Exclude mov %rbp, %rsp from this check. */
|
|
&& ginsn_get_src_reg (src1) != REG_FP)
|
|
{
|
|
/* mov %reg, %rsp. */
|
|
/* A previous mov %rsp, %reg must have been seen earlier for this to be
|
|
an OK for stack manipulation. */
|
|
if (state->scratch[ginsn_get_src_reg (src1)].base != REG_CFA
|
|
|| state->scratch[ginsn_get_src_reg (src1)].state != CFI_IN_REG)
|
|
{
|
|
possibly_untraceable = true;
|
|
}
|
|
}
|
|
/* Check add/sub/and insn usage when CFA base register is REG_SP.
|
|
Any stack size manipulation, including stack realignment is not allowed
|
|
if CFA base register is REG_SP. */
|
|
else if (ginsn_get_dst_type (dst) == GINSN_DST_REG
|
|
&& ginsn_get_dst_reg (dst) == REG_SP
|
|
&& (((ginsn->type == GINSN_TYPE_ADD || ginsn->type == GINSN_TYPE_SUB)
|
|
&& ginsn_get_src_type (src2) != GINSN_SRC_IMM)
|
|
|| ginsn->type == GINSN_TYPE_AND
|
|
|| ginsn->type == GINSN_TYPE_OTHER))
|
|
possibly_untraceable = true;
|
|
/* If a register save operation is seen when REG_SP is untraceable,
|
|
CFI cannot be synthesized for register saves, hence bail out. */
|
|
else if (ginsn_scfi_save_reg_p (ginsn, state) && !state->traceable_p)
|
|
{
|
|
sp_untraceable_p = 1;
|
|
/* If, however, the register save is an REG_FP-based, indirect mov
|
|
like: mov reg, disp(%rbp) and CFA base register is REG_BP,
|
|
untraceable REG_SP is not a problem. */
|
|
if (ginsn->type == GINSN_TYPE_MOV
|
|
&& ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT
|
|
&& (ginsn_get_dst_reg (dst) == REG_FP
|
|
&& state->regs[REG_CFA].base == REG_FP))
|
|
sp_untraceable_p = 0;
|
|
}
|
|
else if (ginsn_scfi_restore_reg_p (ginsn, state) && !state->traceable_p)
|
|
{
|
|
if (ginsn->type == GINSN_TYPE_MOV
|
|
&& ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT
|
|
&& (ginsn_get_src_reg (src1) == REG_SP
|
|
|| (ginsn_get_src_reg (src1) == REG_FP
|
|
&& state->regs[REG_CFA].base != REG_FP)))
|
|
sp_untraceable_p = 1;
|
|
}
|
|
|
|
if (possibly_untraceable)
|
|
{
|
|
/* See Rule 2. For SP-based CFA, this makes CFA tracking not possible.
|
|
Propagate now to caller. */
|
|
if (state->regs[REG_CFA].base == REG_SP)
|
|
sp_untraceable_p = 1;
|
|
else if (state->traceable_p)
|
|
{
|
|
/* An extension of Rule 2.
|
|
For FP-based CFA, this may be a problem *if* certain specific
|
|
changes to the SCFI state are seen beyond this point, e.g.,
|
|
register save / restore from stack. */
|
|
gas_assert (state->regs[REG_CFA].base == REG_FP);
|
|
/* Simply make a note in the SCFI state object for now and
|
|
continue. Indicate an error when register save / restore
|
|
for callee-saved registers is seen. */
|
|
sp_untraceable_p = 0;
|
|
state->traceable_p = false;
|
|
}
|
|
}
|
|
|
|
if (sp_untraceable_p)
|
|
as_bad_where (ginsn->file, ginsn->line,
|
|
_("SCFI: unsupported stack manipulation pattern"));
|
|
|
|
return sp_untraceable_p;
|
|
}
|
|
|
|
static int
|
|
verify_heuristic_symmetrical_restore_reg (scfi_stateS *state, ginsnS* ginsn)
|
|
{
|
|
int sym_restore = true;
|
|
offsetT expected_offset = 0;
|
|
struct ginsn_src *src1;
|
|
struct ginsn_dst *dst;
|
|
unsigned int reg;
|
|
|
|
/* Rule 4: Save and Restore of callee-saved registers must be symmetrical.
|
|
It is expected that value of the saved register is restored correctly.
|
|
E.g.,
|
|
push reg1
|
|
push reg2
|
|
...
|
|
body of func which uses reg1 , reg2 as scratch,
|
|
and may be even spills them to stack.
|
|
...
|
|
pop reg2
|
|
pop reg1
|
|
It is difficult to verify the Rule 4 in all cases. For the SCFI machinery,
|
|
it is difficult to separate prologue-epilogue from the body of the function
|
|
|
|
Hence, the SCFI machinery at this time, should only warn on an asymetrical
|
|
restore. */
|
|
src1 = ginsn_get_src1 (ginsn);
|
|
dst = ginsn_get_dst (ginsn);
|
|
reg = ginsn_get_dst_reg (dst);
|
|
|
|
/* For non callee-saved registers, calling the API is meaningless. */
|
|
if (!ginsn_track_reg_p (ginsn_get_dst_reg (dst), GINSN_GEN_SCFI))
|
|
return sym_restore;
|
|
|
|
/* The register must have been saved on stack, for sure. */
|
|
gas_assert (state->regs[reg].state == CFI_ON_STACK);
|
|
gas_assert (state->regs[reg].base == REG_CFA);
|
|
|
|
if ((ginsn->type == GINSN_TYPE_MOV
|
|
|| ginsn->type == GINSN_TYPE_LOAD)
|
|
&& ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT
|
|
&& (ginsn_get_src_reg (src1) == REG_SP
|
|
|| (ginsn_get_src_reg (src1) == REG_FP
|
|
&& state->regs[REG_CFA].base == REG_FP)))
|
|
{
|
|
/* mov disp(%rsp), reg. */
|
|
/* mov disp(%rbp), reg. */
|
|
expected_offset = (((ginsn_get_src_reg (src1) == REG_SP)
|
|
? -state->stack_size
|
|
: state->regs[REG_FP].offset)
|
|
+ ginsn_get_src_disp (src1));
|
|
}
|
|
|
|
sym_restore = (expected_offset == state->regs[reg].offset);
|
|
|
|
return sym_restore;
|
|
}
|
|
|
|
/* Perform symbolic execution of the GINSN and update its list of scfi_ops.
|
|
scfi_ops are later used to directly generate the DWARF CFI directives.
|
|
Also update the SCFI state object STATE for the caller. */
|
|
|
|
static int
|
|
gen_scfi_ops (ginsnS *ginsn, scfi_stateS *state)
|
|
{
|
|
int ret = 0;
|
|
offsetT offset;
|
|
struct ginsn_src *src1;
|
|
struct ginsn_src *src2;
|
|
struct ginsn_dst *dst;
|
|
|
|
if (!ginsn || !state)
|
|
ret = 1;
|
|
|
|
/* For the first ginsn (of type GINSN_TYPE_SYMBOL) in the gbb, generate
|
|
the SCFI op with DW_CFA_def_cfa. Note that the register and offset are
|
|
target-specific. */
|
|
if (GINSN_F_FUNC_BEGIN_P (ginsn))
|
|
{
|
|
scfi_op_add_def_cfa (state, ginsn, REG_SP, SCFI_INIT_CFA_OFFSET);
|
|
state->stack_size += SCFI_INIT_CFA_OFFSET;
|
|
return ret;
|
|
}
|
|
|
|
src1 = ginsn_get_src1 (ginsn);
|
|
src2 = ginsn_get_src2 (ginsn);
|
|
dst = ginsn_get_dst (ginsn);
|
|
|
|
ret = verify_heuristic_traceable_stack_manipulation (ginsn, state);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = verify_heuristic_traceable_reg_fp (ginsn, state);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (ginsn->dst.type)
|
|
{
|
|
case GINSN_DST_REG:
|
|
switch (ginsn->type)
|
|
{
|
|
case GINSN_TYPE_MOV:
|
|
if (ginsn_get_src_type (src1) == GINSN_SRC_REG
|
|
&& ginsn_get_src_reg (src1) == REG_SP
|
|
&& ginsn_get_dst_reg (dst) == REG_FP
|
|
&& state->regs[REG_CFA].base == REG_SP)
|
|
{
|
|
/* mov %rsp, %rbp. */
|
|
scfi_op_add_def_cfa_reg (state, ginsn, ginsn_get_dst_reg (dst));
|
|
}
|
|
else if (ginsn_get_src_type (src1) == GINSN_SRC_REG
|
|
&& ginsn_get_src_reg (src1) == REG_FP
|
|
&& ginsn_get_dst_reg (dst) == REG_SP
|
|
&& state->regs[REG_CFA].base == REG_FP)
|
|
{
|
|
/* mov %rbp, %rsp. */
|
|
state->stack_size = -state->regs[REG_FP].offset;
|
|
scfi_op_add_def_cfa_reg (state, ginsn, ginsn_get_dst_reg (dst));
|
|
state->traceable_p = true;
|
|
}
|
|
else if (ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT
|
|
&& (ginsn_get_src_reg (src1) == REG_SP
|
|
|| ginsn_get_src_reg (src1) == REG_FP)
|
|
&& ginsn_track_reg_p (ginsn_get_dst_reg (dst), GINSN_GEN_SCFI))
|
|
{
|
|
/* mov disp(%rsp), reg. */
|
|
/* mov disp(%rbp), reg. */
|
|
if (verify_heuristic_symmetrical_restore_reg (state, ginsn))
|
|
{
|
|
scfi_state_restore_reg (state, ginsn_get_dst_reg (dst));
|
|
scfi_op_add_cfa_restore (ginsn, ginsn_get_dst_reg (dst));
|
|
}
|
|
else
|
|
as_warn_where (ginsn->file, ginsn->line,
|
|
_("SCFI: asymetrical register restore"));
|
|
}
|
|
else if (ginsn_get_src_type (src1) == GINSN_SRC_REG
|
|
&& ginsn_get_dst_type (dst) == GINSN_DST_REG
|
|
&& ginsn_get_src_reg (src1) == REG_SP)
|
|
{
|
|
/* mov %rsp, %reg. */
|
|
/* The value of rsp is taken directly from state->stack_size.
|
|
IMP: The workflow in gen_scfi_ops must keep it updated.
|
|
PS: Not taking the value from state->scratch[REG_SP] is
|
|
intentional. */
|
|
state->scratch[ginsn_get_dst_reg (dst)].base = REG_CFA;
|
|
state->scratch[ginsn_get_dst_reg (dst)].offset = -state->stack_size;
|
|
state->scratch[ginsn_get_dst_reg (dst)].state = CFI_IN_REG;
|
|
}
|
|
else if (ginsn_get_src_type (src1) == GINSN_SRC_REG
|
|
&& ginsn_get_dst_type (dst) == GINSN_DST_REG
|
|
&& ginsn_get_dst_reg (dst) == REG_SP)
|
|
{
|
|
/* mov %reg, %rsp. */
|
|
/* Keep the value of REG_SP updated. */
|
|
if (state->scratch[ginsn_get_src_reg (src1)].state == CFI_IN_REG)
|
|
{
|
|
state->stack_size = -state->scratch[ginsn_get_src_reg (src1)].offset;
|
|
state->traceable_p = true;
|
|
}
|
|
# if 0
|
|
scfi_state_update_reg (state, ginsn_get_dst_reg (dst),
|
|
state->scratch[ginsn_get_src_reg (src1)].base,
|
|
state->scratch[ginsn_get_src_reg (src1)].offset);
|
|
#endif
|
|
|
|
}
|
|
break;
|
|
case GINSN_TYPE_SUB:
|
|
if (ginsn_get_src_reg (src1) == REG_SP
|
|
&& ginsn_get_dst_reg (dst) == REG_SP)
|
|
{
|
|
/* Stack inc/dec offset, when generated due to stack push and pop is
|
|
target-specific. Use the value encoded in the ginsn. */
|
|
state->stack_size += ginsn_get_src_imm (src2);
|
|
if (state->regs[REG_CFA].base == REG_SP)
|
|
{
|
|
/* push reg. */
|
|
scfi_op_add_cfa_offset_dec (state, ginsn, ginsn_get_src_imm (src2));
|
|
}
|
|
}
|
|
break;
|
|
case GINSN_TYPE_ADD:
|
|
if (ginsn_get_src_reg (src1) == REG_SP
|
|
&& ginsn_get_dst_reg (dst) == REG_SP)
|
|
{
|
|
/* Stack inc/dec offset is target-specific. Use the value
|
|
encoded in the ginsn. */
|
|
state->stack_size -= ginsn_get_src_imm (src2);
|
|
/* pop %reg affects CFA offset only if CFA is currently
|
|
stack-pointer based. */
|
|
if (state->regs[REG_CFA].base == REG_SP)
|
|
{
|
|
scfi_op_add_cfa_offset_inc (state, ginsn, ginsn_get_src_imm (src2));
|
|
}
|
|
}
|
|
else if (ginsn_get_src_reg (src1) == REG_FP
|
|
&& ginsn_get_dst_reg (dst) == REG_SP
|
|
&& state->regs[REG_CFA].base == REG_FP)
|
|
{
|
|
/* FIXME - what is this for ? */
|
|
state->stack_size = 0 - (state->regs[REG_FP].offset + ginsn_get_src_imm (src2));
|
|
}
|
|
break;
|
|
case GINSN_TYPE_LOAD:
|
|
/* If this is a load from stack. */
|
|
if (ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT
|
|
&& (ginsn_get_src_reg (src1) == REG_SP
|
|
|| (ginsn_get_src_reg (src1) == REG_FP
|
|
&& state->regs[REG_CFA].base == REG_FP)))
|
|
{
|
|
/* pop %rbp when CFA tracking is REG_FP based. */
|
|
if (ginsn_get_dst_reg (dst) == REG_FP
|
|
&& state->regs[REG_CFA].base == REG_FP)
|
|
{
|
|
scfi_op_add_def_cfa_reg (state, ginsn, REG_SP);
|
|
if (state->regs[REG_CFA].offset != state->stack_size)
|
|
scfi_op_add_cfa_offset_inc (state, ginsn,
|
|
(state->regs[REG_CFA].offset - state->stack_size));
|
|
}
|
|
if (ginsn_track_reg_p (ginsn_get_dst_reg (dst), GINSN_GEN_SCFI))
|
|
{
|
|
if (verify_heuristic_symmetrical_restore_reg (state, ginsn))
|
|
{
|
|
scfi_state_restore_reg (state, ginsn_get_dst_reg (dst));
|
|
scfi_op_add_cfa_restore (ginsn, ginsn_get_dst_reg (dst));
|
|
}
|
|
else
|
|
as_warn_where (ginsn->file, ginsn->line,
|
|
_("SCFI: asymetrical register restore"));
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case GINSN_DST_INDIRECT:
|
|
/* Some operations with an indirect access to memory (or even to stack)
|
|
may still be uninteresting for SCFI purpose (e.g, addl %edx, -32(%rsp)
|
|
in x86). In case of x86_64, these can neither be a register
|
|
save / unsave, nor can alter the stack size.
|
|
PS: This condition may need to be revisited for other arches. */
|
|
if (ginsn->type == GINSN_TYPE_ADD || ginsn->type == GINSN_TYPE_SUB
|
|
|| ginsn->type == GINSN_TYPE_AND)
|
|
break;
|
|
gas_assert (ginsn->type == GINSN_TYPE_MOV
|
|
|| ginsn->type == GINSN_TYPE_STORE
|
|
|| ginsn->type == GINSN_TYPE_LOAD);
|
|
/* mov reg, disp(%rbp) */
|
|
/* mov reg, disp(%rsp) */
|
|
if (ginsn_scfi_save_reg_p (ginsn, state))
|
|
{
|
|
if (ginsn_get_dst_reg (dst) == REG_SP)
|
|
{
|
|
/* mov reg, disp(%rsp) */
|
|
offset = 0 - state->stack_size + ginsn_get_dst_disp (dst);
|
|
scfi_state_save_reg (state, ginsn_get_src_reg (src1), REG_CFA, offset);
|
|
scfi_op_add_cfi_offset (state, ginsn, ginsn_get_src_reg (src1));
|
|
}
|
|
else if (ginsn_get_dst_reg (dst) == REG_FP)
|
|
{
|
|
gas_assert (state->regs[REG_CFA].base == REG_FP);
|
|
/* mov reg, disp(%rbp) */
|
|
offset = 0 - state->regs[REG_CFA].offset + ginsn_get_dst_disp (dst);
|
|
scfi_state_save_reg (state, ginsn_get_src_reg (src1), REG_CFA, offset);
|
|
scfi_op_add_cfi_offset (state, ginsn, ginsn_get_src_reg (src1));
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* Skip GINSN_DST_UNKNOWN and GINSN_DST_MEM as they are uninteresting
|
|
currently for SCFI. */
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Recursively perform forward flow of the (unwind information) SCFI STATE
|
|
starting at basic block GBB.
|
|
|
|
The core of forward flow process takes the SCFI state at the entry of a bb
|
|
and updates it incrementally as per the semantics of each ginsn in the bb.
|
|
|
|
Returns error code, if any. */
|
|
|
|
static int
|
|
forward_flow_scfi_state (gcfgS *gcfg, gbbS *gbb, scfi_stateS *state)
|
|
{
|
|
ginsnS *ginsn;
|
|
gbbS *prev_bb;
|
|
gedgeS *gedge = NULL;
|
|
int ret = 0;
|
|
|
|
if (gbb->visited)
|
|
{
|
|
/* Check that the SCFI state is the same as previous. */
|
|
ret = cmp_scfi_state (state, gbb->entry_state);
|
|
if (ret)
|
|
as_bad (_("SCFI: Bad CFI propagation perhaps"));
|
|
return ret;
|
|
}
|
|
|
|
gbb->visited = true;
|
|
|
|
gbb->entry_state = XCNEW (scfi_stateS);
|
|
memcpy (gbb->entry_state, state, sizeof (scfi_stateS));
|
|
|
|
/* Perform symbolic execution of each ginsn in the gbb and update the
|
|
scfi_ops list of each ginsn (and also update the STATE object). */
|
|
bb_for_each_insn(gbb, ginsn)
|
|
{
|
|
ret = gen_scfi_ops (ginsn, state);
|
|
if (ret)
|
|
goto fail;
|
|
}
|
|
|
|
gbb->exit_state = XCNEW (scfi_stateS);
|
|
memcpy (gbb->exit_state, state, sizeof (scfi_stateS));
|
|
|
|
/* Forward flow the SCFI state. Currently, we process the next basic block
|
|
in DFS order. But any forward traversal order should be fine. */
|
|
prev_bb = gbb;
|
|
if (gbb->num_out_gedges)
|
|
{
|
|
bb_for_each_edge(gbb, gedge)
|
|
{
|
|
gbb = gedge->dst_bb;
|
|
/* Ensure that the state is the one from the exit of the prev bb. */
|
|
memcpy (state, prev_bb->exit_state, sizeof (scfi_stateS));
|
|
if (gbb->visited)
|
|
{
|
|
ret = cmp_scfi_state (gbb->entry_state, state);
|
|
if (ret)
|
|
goto fail;
|
|
}
|
|
|
|
if (!gedge->visited)
|
|
{
|
|
gedge->visited = true;
|
|
|
|
/* Entry SCFI state for the destination bb of the edge is the
|
|
same as the exit SCFI state of the source bb of the edge. */
|
|
memcpy (state, prev_bb->exit_state, sizeof (scfi_stateS));
|
|
ret = forward_flow_scfi_state (gcfg, gbb, state);
|
|
if (ret)
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
|
|
if (gedge)
|
|
gedge->visited = true;
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
backward_flow_scfi_state (const symbolS *func ATTRIBUTE_UNUSED, gcfgS *gcfg)
|
|
{
|
|
gbbS **prog_order_bbs;
|
|
gbbS **restore_bbs;
|
|
gbbS *current_bb;
|
|
gbbS *prev_bb;
|
|
gbbS *dst_bb;
|
|
ginsnS *ginsn;
|
|
gedgeS *gedge = NULL;
|
|
|
|
int ret = 0;
|
|
uint64_t i, j;
|
|
|
|
/* Basic blocks in reverse program order. */
|
|
prog_order_bbs = XCNEWVEC (gbbS *, gcfg->num_gbbs);
|
|
/* Basic blocks for which CFI remember op needs to be generated. */
|
|
restore_bbs = XCNEWVEC (gbbS *, gcfg->num_gbbs);
|
|
|
|
gcfg_get_bbs_in_prog_order (gcfg, prog_order_bbs);
|
|
|
|
i = gcfg->num_gbbs - 1;
|
|
/* Traverse in reverse program order. */
|
|
while (i > 0)
|
|
{
|
|
current_bb = prog_order_bbs[i];
|
|
prev_bb = prog_order_bbs[i-1];
|
|
if (cmp_scfi_state (prev_bb->exit_state, current_bb->entry_state))
|
|
{
|
|
/* Candidate for .cfi_restore_state found. */
|
|
ginsn = bb_get_first_ginsn (current_bb);
|
|
scfi_op_add_cfi_restore_state (ginsn);
|
|
/* Memorize current_bb now to find location for its remember state
|
|
later. */
|
|
restore_bbs[i] = current_bb;
|
|
}
|
|
else
|
|
{
|
|
bb_for_each_edge (current_bb, gedge)
|
|
{
|
|
dst_bb = gedge->dst_bb;
|
|
for (j = 0; j < gcfg->num_gbbs; j++)
|
|
if (restore_bbs[j] == dst_bb)
|
|
{
|
|
ginsn = bb_get_last_ginsn (current_bb);
|
|
scfi_op_add_cfi_remember_state (ginsn);
|
|
/* Remove the memorised restore_bb from the list. */
|
|
restore_bbs[j] = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
i--;
|
|
}
|
|
|
|
/* All .cfi_restore_state pseudo-ops must have a corresponding
|
|
.cfi_remember_state by now. */
|
|
for (j = 0; j < gcfg->num_gbbs; j++)
|
|
if (restore_bbs[j] != NULL)
|
|
{
|
|
ret = 1;
|
|
break;
|
|
}
|
|
|
|
free (restore_bbs);
|
|
free (prog_order_bbs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Synthesize DWARF CFI for a function. */
|
|
|
|
int
|
|
scfi_synthesize_dw2cfi (const symbolS *func, gcfgS *gcfg, gbbS *root_bb)
|
|
{
|
|
int ret;
|
|
scfi_stateS *init_state;
|
|
|
|
init_state = XCNEW (scfi_stateS);
|
|
init_state->traceable_p = true;
|
|
|
|
/* Traverse the input GCFG and perform forward flow of information.
|
|
Update the scfi_op(s) per ginsn. */
|
|
ret = forward_flow_scfi_state (gcfg, root_bb, init_state);
|
|
if (ret)
|
|
{
|
|
as_bad (_("SCFI: forward pass failed for func '%s'"), S_GET_NAME (func));
|
|
goto end;
|
|
}
|
|
|
|
ret = backward_flow_scfi_state (func, gcfg);
|
|
if (ret)
|
|
{
|
|
as_bad (_("SCFI: backward pass failed for func '%s'"), S_GET_NAME (func));
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
free (init_state);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
handle_scfi_dot_cfi (ginsnS *ginsn)
|
|
{
|
|
scfi_opS *op;
|
|
|
|
/* Nothing to do. */
|
|
if (!ginsn->scfi_ops)
|
|
return 0;
|
|
|
|
op = *ginsn->scfi_ops;
|
|
if (!op)
|
|
goto bad;
|
|
|
|
while (op)
|
|
{
|
|
switch (op->dw2cfi_op)
|
|
{
|
|
case DW_CFA_def_cfa_register:
|
|
scfi_dot_cfi (DW_CFA_def_cfa_register, op->loc.base, 0, 0, NULL,
|
|
ginsn->sym);
|
|
break;
|
|
case DW_CFA_def_cfa_offset:
|
|
scfi_dot_cfi (DW_CFA_def_cfa_offset, op->loc.base, 0,
|
|
op->loc.offset, NULL, ginsn->sym);
|
|
break;
|
|
case DW_CFA_def_cfa:
|
|
scfi_dot_cfi (DW_CFA_def_cfa, op->loc.base, 0, op->loc.offset,
|
|
NULL, ginsn->sym);
|
|
break;
|
|
case DW_CFA_offset:
|
|
scfi_dot_cfi (DW_CFA_offset, op->reg, 0, op->loc.offset, NULL,
|
|
ginsn->sym);
|
|
break;
|
|
case DW_CFA_restore:
|
|
scfi_dot_cfi (DW_CFA_restore, op->reg, 0, 0, NULL, ginsn->sym);
|
|
break;
|
|
case DW_CFA_remember_state:
|
|
scfi_dot_cfi (DW_CFA_remember_state, 0, 0, 0, NULL, ginsn->sym);
|
|
break;
|
|
case DW_CFA_restore_state:
|
|
scfi_dot_cfi (DW_CFA_restore_state, 0, 0, 0, NULL, ginsn->sym);
|
|
break;
|
|
case CFI_label:
|
|
scfi_dot_cfi (CFI_label, 0, 0, 0, op->op_data->name, ginsn->sym);
|
|
free ((char *) op->op_data->name);
|
|
break;
|
|
case CFI_signal_frame:
|
|
scfi_dot_cfi (CFI_signal_frame, 0, 0, 0, NULL, ginsn->sym);
|
|
break;
|
|
default:
|
|
goto bad;
|
|
break;
|
|
}
|
|
op = op->next;
|
|
}
|
|
|
|
return 0;
|
|
bad:
|
|
as_bad (_("SCFI: Invalid DWARF CFI opcode data"));
|
|
return 1;
|
|
}
|
|
|
|
/* Emit Synthesized DWARF CFI. */
|
|
|
|
int
|
|
scfi_emit_dw2cfi (const symbolS *func)
|
|
{
|
|
struct frch_ginsn_data *frch_gdata;
|
|
ginsnS* ginsn = NULL;
|
|
|
|
frch_gdata = frchain_now->frch_ginsn_data;
|
|
ginsn = frch_gdata->gins_rootP;
|
|
|
|
while (ginsn)
|
|
{
|
|
switch (ginsn->type)
|
|
{
|
|
case GINSN_TYPE_SYMBOL:
|
|
/* .cfi_startproc and .cfi_endproc pseudo-ops. */
|
|
if (GINSN_F_FUNC_BEGIN_P (ginsn))
|
|
{
|
|
scfi_dot_cfi_startproc (frch_gdata->start_addr);
|
|
break;
|
|
}
|
|
else if (GINSN_F_FUNC_END_P (ginsn))
|
|
{
|
|
scfi_dot_cfi_endproc (ginsn->sym);
|
|
break;
|
|
}
|
|
/* Fall through. */
|
|
case GINSN_TYPE_ADD:
|
|
case GINSN_TYPE_AND:
|
|
case GINSN_TYPE_CALL:
|
|
case GINSN_TYPE_JUMP:
|
|
case GINSN_TYPE_JUMP_COND:
|
|
case GINSN_TYPE_MOV:
|
|
case GINSN_TYPE_LOAD:
|
|
case GINSN_TYPE_PHANTOM:
|
|
case GINSN_TYPE_STORE:
|
|
case GINSN_TYPE_SUB:
|
|
case GINSN_TYPE_OTHER:
|
|
case GINSN_TYPE_RETURN:
|
|
|
|
/* For all other SCFI ops, invoke the handler. */
|
|
if (ginsn->scfi_ops)
|
|
handle_scfi_dot_cfi (ginsn);
|
|
break;
|
|
|
|
default:
|
|
/* No other GINSN_TYPE_* expected. */
|
|
as_bad (_("SCFI: bad ginsn for func '%s'"),
|
|
S_GET_NAME (func));
|
|
break;
|
|
}
|
|
ginsn = ginsn->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
int
|
|
scfi_emit_dw2cfi (const symbolS *func ATTRIBUTE_UNUSED)
|
|
{
|
|
as_bad (_("SCFI: unsupported for target"));
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
scfi_synthesize_dw2cfi (const symbolS *func ATTRIBUTE_UNUSED,
|
|
gcfgS *gcfg ATTRIBUTE_UNUSED,
|
|
gbbS *root_bb ATTRIBUTE_UNUSED)
|
|
{
|
|
as_bad (_("SCFI: unsupported for target"));
|
|
return 1;
|
|
}
|
|
|
|
#endif /* defined (TARGET_USE_SCFI) && defined (TARGET_USE_GINSN). */
|