ff4ff35918
Red Bear OS is a full fork. All sources must be available from git clone with zero network access. Removed gitignore rules that excluded fetched source trees under recipes/*/source/, local/recipes/kde/*/source/, local/recipes/qt/*/source/, and vendor source trees. Build artifacts (target/, build/, source.tar, *.o, *.so) remain excluded. 127291 files added — kernel, relibc, base, bootloader, pkgar, all KDE/Qt frameworks, mesa, wayland, DRM drivers, and every other recipe source.
1868 lines
54 KiB
C
1868 lines
54 KiB
C
/* tc-bpf.c -- Assembler for the Linux eBPF.
|
|
Copyright (C) 2019-2024 Free Software Foundation, Inc.
|
|
Contributed by Oracle, 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 "subsegs.h"
|
|
#include "symcat.h"
|
|
#include "opcode/bpf.h"
|
|
#include "elf/common.h"
|
|
#include "elf/bpf.h"
|
|
#include "dwarf2dbg.h"
|
|
#include "libiberty.h"
|
|
#include <ctype.h>
|
|
|
|
/* Data structure representing a parsed BPF instruction. */
|
|
|
|
struct bpf_insn
|
|
{
|
|
enum bpf_insn_id id;
|
|
int size; /* Instruction size in bytes. */
|
|
bpf_insn_word opcode;
|
|
uint8_t dst;
|
|
uint8_t src;
|
|
expressionS offset16;
|
|
expressionS imm32;
|
|
expressionS imm64;
|
|
expressionS disp16;
|
|
expressionS disp32;
|
|
|
|
unsigned int has_dst : 1;
|
|
unsigned int has_src : 1;
|
|
unsigned int has_offset16 : 1;
|
|
unsigned int has_disp16 : 1;
|
|
unsigned int has_disp32 : 1;
|
|
unsigned int has_imm32 : 1;
|
|
unsigned int has_imm64 : 1;
|
|
|
|
unsigned int is_relaxable : 1;
|
|
expressionS *relaxed_exp;
|
|
};
|
|
|
|
const char comment_chars[] = "#";
|
|
const char line_comment_chars[] = "#";
|
|
const char line_separator_chars[] = ";`";
|
|
const char EXP_CHARS[] = "eE";
|
|
const char FLT_CHARS[] = "fFdD";
|
|
|
|
/* Like s_lcomm_internal in gas/read.c but the alignment string
|
|
is allowed to be optional. */
|
|
|
|
static symbolS *
|
|
pe_lcomm_internal (int needs_align, symbolS *symbolP, addressT size)
|
|
{
|
|
addressT align = 0;
|
|
|
|
SKIP_WHITESPACE ();
|
|
|
|
if (needs_align
|
|
&& *input_line_pointer == ',')
|
|
{
|
|
align = parse_align (needs_align - 1);
|
|
|
|
if (align == (addressT) -1)
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
if (size >= 8)
|
|
align = 3;
|
|
else if (size >= 4)
|
|
align = 2;
|
|
else if (size >= 2)
|
|
align = 1;
|
|
else
|
|
align = 0;
|
|
}
|
|
|
|
bss_alloc (symbolP, size, align);
|
|
return symbolP;
|
|
}
|
|
|
|
static void
|
|
pe_lcomm (int needs_align)
|
|
{
|
|
s_comm_internal (needs_align * 2, pe_lcomm_internal);
|
|
}
|
|
|
|
/* The target specific pseudo-ops which we support. */
|
|
const pseudo_typeS md_pseudo_table[] =
|
|
{
|
|
{ "half", cons, 2 },
|
|
{ "word", cons, 4 },
|
|
{ "dword", cons, 8 },
|
|
{ "lcomm", pe_lcomm, 1 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
|
|
|
|
/* Command-line options processing. */
|
|
|
|
enum options
|
|
{
|
|
OPTION_LITTLE_ENDIAN = OPTION_MD_BASE,
|
|
OPTION_BIG_ENDIAN,
|
|
OPTION_XBPF,
|
|
OPTION_DIALECT,
|
|
OPTION_ISA_SPEC,
|
|
OPTION_NO_RELAX,
|
|
};
|
|
|
|
struct option md_longopts[] =
|
|
{
|
|
{ "EL", no_argument, NULL, OPTION_LITTLE_ENDIAN },
|
|
{ "EB", no_argument, NULL, OPTION_BIG_ENDIAN },
|
|
{ "mxbpf", no_argument, NULL, OPTION_XBPF },
|
|
{ "mdialect", required_argument, NULL, OPTION_DIALECT},
|
|
{ "misa-spec", required_argument, NULL, OPTION_ISA_SPEC},
|
|
{ "mno-relax", no_argument, NULL, OPTION_NO_RELAX},
|
|
{ NULL, no_argument, NULL, 0 },
|
|
};
|
|
|
|
size_t md_longopts_size = sizeof (md_longopts);
|
|
|
|
const char * md_shortopts = "";
|
|
|
|
/* BPF supports little-endian and big-endian variants. The following
|
|
global records what endianness to use. It can be configured using
|
|
command-line options. It defaults to the host endianness
|
|
initialized in md_begin. */
|
|
|
|
static int set_target_endian = 0;
|
|
extern int target_big_endian;
|
|
|
|
/* Whether to relax branch instructions. Default is yes. Can be
|
|
changed using the -mno-relax command line option. */
|
|
|
|
static int do_relax = 1;
|
|
|
|
/* The ISA specification can be one of BPF_V1, BPF_V2, BPF_V3, BPF_V4
|
|
or BPF_XPBF. The ISA spec to use can be configured using
|
|
command-line options. It defaults to the latest BPF spec. */
|
|
|
|
static int isa_spec = BPF_V4;
|
|
|
|
/* The assembler supports two different dialects: "normal" syntax and
|
|
"pseudoc" syntax. The dialect to use can be configured using
|
|
command-line options. */
|
|
|
|
enum target_asm_dialect
|
|
{
|
|
DIALECT_NORMAL,
|
|
DIALECT_PSEUDOC
|
|
};
|
|
|
|
static int asm_dialect = DIALECT_NORMAL;
|
|
|
|
int
|
|
md_parse_option (int c, const char * arg)
|
|
{
|
|
switch (c)
|
|
{
|
|
case OPTION_BIG_ENDIAN:
|
|
set_target_endian = 1;
|
|
target_big_endian = 1;
|
|
break;
|
|
case OPTION_LITTLE_ENDIAN:
|
|
set_target_endian = 0;
|
|
target_big_endian = 0;
|
|
break;
|
|
case OPTION_DIALECT:
|
|
if (strcmp (arg, "normal") == 0)
|
|
asm_dialect = DIALECT_NORMAL;
|
|
else if (strcmp (arg, "pseudoc") == 0)
|
|
asm_dialect = DIALECT_PSEUDOC;
|
|
else
|
|
as_fatal (_("-mdialect=%s is not valid. Expected normal or pseudoc"),
|
|
arg);
|
|
break;
|
|
case OPTION_ISA_SPEC:
|
|
if (strcmp (arg, "v1") == 0)
|
|
isa_spec = BPF_V1;
|
|
else if (strcmp (arg, "v2") == 0)
|
|
isa_spec = BPF_V2;
|
|
else if (strcmp (arg, "v3") == 0)
|
|
isa_spec = BPF_V3;
|
|
else if (strcmp (arg, "v4") == 0)
|
|
isa_spec = BPF_V4;
|
|
else if (strcmp (arg, "xbpf") == 0)
|
|
isa_spec = BPF_XBPF;
|
|
else
|
|
as_fatal (_("-misa-spec=%s is not valid. Expected v1, v2, v3, v4 o xbpf"),
|
|
arg);
|
|
break;
|
|
case OPTION_XBPF:
|
|
/* This is an alias for -misa-spec=xbpf. */
|
|
isa_spec = BPF_XBPF;
|
|
break;
|
|
case OPTION_NO_RELAX:
|
|
do_relax = 0;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
md_show_usage (FILE * stream)
|
|
{
|
|
fprintf (stream, _("\nBPF options:\n"));
|
|
fprintf (stream, _("\
|
|
BPF options:\n\
|
|
-EL generate code for a little endian machine\n\
|
|
-EB generate code for a big endian machine\n\
|
|
-mdialect=DIALECT set the assembly dialect (normal, pseudoc)\n\
|
|
-misa-spec set the BPF ISA spec (v1, v2, v3, v4, xbpf)\n\
|
|
-mxbpf alias for -misa-spec=xbpf\n"));
|
|
}
|
|
|
|
|
|
/* This function is called once, at assembler startup time. This
|
|
should set up all the tables, etc that the MD part of the assembler
|
|
needs. */
|
|
|
|
void
|
|
md_begin (void)
|
|
{
|
|
/* If not specified in the command line, use the host
|
|
endianness. */
|
|
if (!set_target_endian)
|
|
{
|
|
#ifdef WORDS_BIGENDIAN
|
|
target_big_endian = 1;
|
|
#else
|
|
target_big_endian = 0;
|
|
#endif
|
|
}
|
|
|
|
/* Ensure that lines can begin with '*' in BPF store pseudoc instruction. */
|
|
lex_type['*'] |= LEX_BEGIN_NAME;
|
|
|
|
/* Set the machine type. */
|
|
bfd_default_set_arch_mach (stdoutput, bfd_arch_bpf, bfd_mach_bpf);
|
|
}
|
|
|
|
/* Round up a section size to the appropriate boundary. */
|
|
|
|
valueT
|
|
md_section_align (segT segment, valueT size)
|
|
{
|
|
int align = bfd_section_alignment (segment);
|
|
|
|
return ((size + (1 << align) - 1) & -(1 << align));
|
|
}
|
|
|
|
/* Return non-zero if the indicated VALUE has overflowed the maximum
|
|
range expressible by an signed number with the indicated number of
|
|
BITS. */
|
|
|
|
static bool
|
|
signed_overflow (offsetT value, unsigned bits)
|
|
{
|
|
offsetT lim;
|
|
if (bits >= sizeof (offsetT) * 8)
|
|
return false;
|
|
lim = (offsetT) 1 << (bits - 1);
|
|
return (value < -lim || value >= lim);
|
|
}
|
|
|
|
/* Return non-zero if the two's complement encoding of VALUE would
|
|
overflow an immediate field of width BITS bits. */
|
|
|
|
static bool
|
|
immediate_overflow (int64_t value, unsigned bits)
|
|
{
|
|
if (value < 0)
|
|
return signed_overflow (value, bits);
|
|
else
|
|
{
|
|
valueT lim;
|
|
|
|
if (bits >= sizeof (valueT) * 8)
|
|
return false;
|
|
|
|
lim = (valueT) 1 << bits;
|
|
return ((valueT) value >= lim);
|
|
}
|
|
}
|
|
|
|
|
|
/* Functions concerning relocs. */
|
|
|
|
/* The location from which a PC relative jump should be calculated,
|
|
given a PC relative reloc. */
|
|
|
|
long
|
|
md_pcrel_from_section (fixS *fixP, segT sec)
|
|
{
|
|
if (fixP->fx_addsy != (symbolS *) NULL
|
|
&& (! S_IS_DEFINED (fixP->fx_addsy)
|
|
|| (S_GET_SEGMENT (fixP->fx_addsy) != sec)
|
|
|| S_IS_EXTERNAL (fixP->fx_addsy)
|
|
|| S_IS_WEAK (fixP->fx_addsy)))
|
|
{
|
|
/* The symbol is undefined (or is defined but not in this section).
|
|
Let the linker figure it out. */
|
|
return 0;
|
|
}
|
|
|
|
return fixP->fx_where + fixP->fx_frag->fr_address;
|
|
}
|
|
|
|
/* Write a value out to the object file, using the appropriate endianness. */
|
|
|
|
void
|
|
md_number_to_chars (char * buf, valueT val, int n)
|
|
{
|
|
if (target_big_endian)
|
|
number_to_chars_bigendian (buf, val, n);
|
|
else
|
|
number_to_chars_littleendian (buf, val, n);
|
|
}
|
|
|
|
arelent *
|
|
tc_gen_reloc (asection *sec ATTRIBUTE_UNUSED, fixS *fixP)
|
|
{
|
|
bfd_reloc_code_real_type r_type = fixP->fx_r_type;
|
|
arelent *reloc;
|
|
|
|
reloc = XNEW (arelent);
|
|
|
|
if (fixP->fx_pcrel)
|
|
{
|
|
r_type = (r_type == BFD_RELOC_8 ? BFD_RELOC_8_PCREL
|
|
: r_type == BFD_RELOC_16 ? BFD_RELOC_16_PCREL
|
|
: r_type == BFD_RELOC_24 ? BFD_RELOC_24_PCREL
|
|
: r_type == BFD_RELOC_32 ? BFD_RELOC_32_PCREL
|
|
: r_type == BFD_RELOC_64 ? BFD_RELOC_64_PCREL
|
|
: r_type);
|
|
}
|
|
|
|
reloc->howto = bfd_reloc_type_lookup (stdoutput, r_type);
|
|
|
|
if (reloc->howto == (reloc_howto_type *) NULL)
|
|
{
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("relocation is not supported"));
|
|
return NULL;
|
|
}
|
|
|
|
//XXX gas_assert (!fixP->fx_pcrel == !reloc->howto->pc_relative);
|
|
|
|
reloc->sym_ptr_ptr = XNEW (asymbol *);
|
|
*reloc->sym_ptr_ptr = symbol_get_bfdsym (fixP->fx_addsy);
|
|
|
|
/* Use fx_offset for these cases. */
|
|
if (fixP->fx_r_type == BFD_RELOC_VTABLE_ENTRY
|
|
|| fixP->fx_r_type == BFD_RELOC_VTABLE_INHERIT)
|
|
reloc->addend = fixP->fx_offset;
|
|
else
|
|
reloc->addend = fixP->fx_addnumber;
|
|
|
|
reloc->address = fixP->fx_frag->fr_address + fixP->fx_where;
|
|
return reloc;
|
|
}
|
|
|
|
|
|
/* Relaxations supported by this assembler. */
|
|
|
|
#define RELAX_BRANCH_ENCODE(uncond, constant, length) \
|
|
((relax_substateT) \
|
|
(0xc0000000 \
|
|
| ((uncond) ? 1 : 0) \
|
|
| ((constant) ? 2 : 0) \
|
|
| ((length) << 2)))
|
|
|
|
#define RELAX_BRANCH_P(i) (((i) & 0xf0000000) == 0xc0000000)
|
|
#define RELAX_BRANCH_LENGTH(i) (((i) >> 2) & 0xff)
|
|
#define RELAX_BRANCH_CONST(i) (((i) & 2) != 0)
|
|
#define RELAX_BRANCH_UNCOND(i) (((i) & 1) != 0)
|
|
|
|
|
|
/* Compute the length of a branch sequence, and adjust the stored
|
|
length accordingly. If FRAG is NULL, the worst-case length is
|
|
returned. */
|
|
|
|
static unsigned
|
|
relaxed_branch_length (fragS *fragp, asection *sec, int update)
|
|
{
|
|
int length, uncond;
|
|
|
|
if (!fragp)
|
|
return 8 * 3;
|
|
|
|
uncond = RELAX_BRANCH_UNCOND (fragp->fr_subtype);
|
|
length = RELAX_BRANCH_LENGTH (fragp->fr_subtype);
|
|
|
|
if (uncond)
|
|
/* Length is the same for both JA and JAL. */
|
|
length = 8;
|
|
else
|
|
{
|
|
if (RELAX_BRANCH_CONST (fragp->fr_subtype))
|
|
{
|
|
int64_t val = fragp->fr_offset;
|
|
|
|
if (val < -32768 || val > 32767)
|
|
length = 8 * 3;
|
|
else
|
|
length = 8;
|
|
}
|
|
else if (fragp->fr_symbol != NULL
|
|
&& S_IS_DEFINED (fragp->fr_symbol)
|
|
&& !S_IS_WEAK (fragp->fr_symbol)
|
|
&& sec == S_GET_SEGMENT (fragp->fr_symbol))
|
|
{
|
|
offsetT val = S_GET_VALUE (fragp->fr_symbol) + fragp->fr_offset;
|
|
val -= fragp->fr_address + fragp->fr_fix;
|
|
|
|
/* Convert to 64-bit words, minus one. */
|
|
val = (val - 8) / 8;
|
|
|
|
/* See if it fits in the signed 16-bits field. */
|
|
if (val < -32768 || val > 32767)
|
|
length = 8 * 3;
|
|
else
|
|
length = 8;
|
|
}
|
|
else
|
|
/* Use short version, and let the linker relax instead, if
|
|
appropriate and if supported. */
|
|
length = 8;
|
|
}
|
|
|
|
if (update)
|
|
fragp->fr_subtype = RELAX_BRANCH_ENCODE (uncond,
|
|
RELAX_BRANCH_CONST (fragp->fr_subtype),
|
|
length);
|
|
|
|
return length;
|
|
}
|
|
|
|
/* Estimate the size of a variant frag before relaxing. */
|
|
|
|
int
|
|
md_estimate_size_before_relax (fragS *fragp, asection *sec)
|
|
{
|
|
return (fragp->fr_var = relaxed_branch_length (fragp, sec, true));
|
|
}
|
|
|
|
/* Read a BPF instruction word from BUF. */
|
|
|
|
static uint64_t
|
|
read_insn_word (bfd_byte *buf)
|
|
{
|
|
return bfd_getb64 (buf);
|
|
}
|
|
|
|
/* Write the given signed 16-bit value in the given BUFFER using the
|
|
target endianness. */
|
|
|
|
static void
|
|
encode_int16 (int16_t value, char *buffer)
|
|
{
|
|
uint16_t val = value;
|
|
|
|
if (target_big_endian)
|
|
{
|
|
buffer[0] = (val >> 8) & 0xff;
|
|
buffer[1] = val & 0xff;
|
|
}
|
|
else
|
|
{
|
|
buffer[1] = (val >> 8) & 0xff;
|
|
buffer[0] = val & 0xff;
|
|
}
|
|
}
|
|
|
|
/* Write the given signed 32-bit value in the given BUFFER using the
|
|
target endianness. */
|
|
|
|
static void
|
|
encode_int32 (int32_t value, char *buffer)
|
|
{
|
|
uint32_t val = value;
|
|
|
|
if (target_big_endian)
|
|
{
|
|
buffer[0] = (val >> 24) & 0xff;
|
|
buffer[1] = (val >> 16) & 0xff;
|
|
buffer[2] = (val >> 8) & 0xff;
|
|
buffer[3] = val & 0xff;
|
|
}
|
|
else
|
|
{
|
|
buffer[3] = (val >> 24) & 0xff;
|
|
buffer[2] = (val >> 16) & 0xff;
|
|
buffer[1] = (val >> 8) & 0xff;
|
|
buffer[0] = value & 0xff;
|
|
}
|
|
}
|
|
|
|
/* Write a BPF instruction to BUF. */
|
|
|
|
static void
|
|
write_insn_bytes (bfd_byte *buf, char *bytes)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 8; ++i)
|
|
md_number_to_chars ((char *) buf + i, (valueT) bytes[i], 1);
|
|
}
|
|
|
|
/* *FRAGP has been relaxed to its final size, and now needs to have
|
|
the bytes inside it modified to conform to the new size.
|
|
|
|
Called after relaxation is finished.
|
|
fragP->fr_type == rs_machine_dependent.
|
|
fragP->fr_subtype is the subtype of what the address relaxed to. */
|
|
|
|
void
|
|
md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED,
|
|
segT sec ATTRIBUTE_UNUSED,
|
|
fragS *fragp ATTRIBUTE_UNUSED)
|
|
{
|
|
bfd_byte *buf = (bfd_byte *) fragp->fr_literal + fragp->fr_fix;
|
|
expressionS exp;
|
|
fixS *fixp;
|
|
bpf_insn_word word;
|
|
int disp_is_known = 0;
|
|
int64_t disp_to_target = 0;
|
|
|
|
uint64_t code;
|
|
|
|
gas_assert (RELAX_BRANCH_P (fragp->fr_subtype));
|
|
|
|
/* Expression to be used in any resulting relocation in the relaxed
|
|
instructions. */
|
|
exp.X_op = O_symbol;
|
|
exp.X_add_symbol = fragp->fr_symbol;
|
|
exp.X_add_number = fragp->fr_offset;
|
|
|
|
gas_assert (fragp->fr_var == RELAX_BRANCH_LENGTH (fragp->fr_subtype));
|
|
|
|
/* Read an instruction word from the instruction to be relaxed, and
|
|
get the code. */
|
|
word = read_insn_word (buf);
|
|
code = (word >> 60) & 0xf;
|
|
|
|
/* Determine whether the 16-bit displacement to the target is known
|
|
at this point. */
|
|
if (RELAX_BRANCH_CONST (fragp->fr_subtype))
|
|
{
|
|
disp_to_target = fragp->fr_offset;
|
|
disp_is_known = 1;
|
|
}
|
|
else if (fragp->fr_symbol != NULL
|
|
&& S_IS_DEFINED (fragp->fr_symbol)
|
|
&& !S_IS_WEAK (fragp->fr_symbol)
|
|
&& sec == S_GET_SEGMENT (fragp->fr_symbol))
|
|
{
|
|
offsetT val = S_GET_VALUE (fragp->fr_symbol) + fragp->fr_offset;
|
|
val -= fragp->fr_address + fragp->fr_fix;
|
|
/* Convert to 64-bit blocks minus one. */
|
|
disp_to_target = (val - 8) / 8;
|
|
disp_is_known = 1;
|
|
}
|
|
|
|
/* The displacement should fit in a signed 32-bit number. */
|
|
if (disp_is_known && signed_overflow (disp_to_target, 32))
|
|
as_bad_where (fragp->fr_file, fragp->fr_line,
|
|
_("signed instruction operand out of range, shall fit in 32 bits"));
|
|
|
|
/* Now relax particular jump instructions. */
|
|
if (code == BPF_CODE_JA)
|
|
{
|
|
/* Unconditional jump.
|
|
JA d16 -> JAL d32 */
|
|
|
|
gas_assert (RELAX_BRANCH_UNCOND (fragp->fr_subtype));
|
|
|
|
if (disp_is_known)
|
|
{
|
|
if (disp_to_target >= -32768 && disp_to_target <= 32767)
|
|
{
|
|
/* 16-bit disp is known and in range. Install a fixup
|
|
for the disp16 if the branch value is not constant.
|
|
This will be resolved by the assembler and units
|
|
converted. */
|
|
|
|
if (!RELAX_BRANCH_CONST (fragp->fr_subtype))
|
|
{
|
|
/* Install fixup for the JA. */
|
|
reloc_howto_type *reloc_howto
|
|
= bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
|
|
if (!reloc_howto)
|
|
abort();
|
|
|
|
fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
|
|
bfd_get_reloc_size (reloc_howto),
|
|
&exp,
|
|
reloc_howto->pc_relative,
|
|
BFD_RELOC_BPF_DISP16);
|
|
fixp->fx_file = fragp->fr_file;
|
|
fixp->fx_line = fragp->fr_line;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* 16-bit disp is known and not in range. Turn the JA
|
|
into a JAL with a 32-bit displacement. */
|
|
char bytes[8] = {0};
|
|
|
|
bytes[0] = ((BPF_CLASS_JMP32|BPF_CODE_JA|BPF_SRC_K) >> 56) & 0xff;
|
|
bytes[1] = (word >> 48) & 0xff;
|
|
bytes[2] = 0; /* disp16 high */
|
|
bytes[3] = 0; /* disp16 lo */
|
|
write_insn_bytes (buf, bytes);
|
|
|
|
/* Install fixup for the JAL. */
|
|
reloc_howto_type *reloc_howto
|
|
= bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP32);
|
|
if (!reloc_howto)
|
|
abort();
|
|
|
|
fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
|
|
bfd_get_reloc_size (reloc_howto),
|
|
&exp,
|
|
reloc_howto->pc_relative,
|
|
BFD_RELOC_BPF_DISP32);
|
|
fixp->fx_file = fragp->fr_file;
|
|
fixp->fx_line = fragp->fr_line;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* The displacement to the target is not known. Do not
|
|
relax. The linker will maybe do it if it chooses to. */
|
|
|
|
reloc_howto_type *reloc_howto = NULL;
|
|
|
|
gas_assert (!RELAX_BRANCH_CONST (fragp->fr_subtype));
|
|
|
|
/* Install fixup for the JA. */
|
|
reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
|
|
if (!reloc_howto)
|
|
abort ();
|
|
|
|
fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
|
|
bfd_get_reloc_size (reloc_howto),
|
|
&exp,
|
|
reloc_howto->pc_relative,
|
|
BFD_RELOC_BPF_DISP16);
|
|
fixp->fx_file = fragp->fr_file;
|
|
fixp->fx_line = fragp->fr_line;
|
|
}
|
|
|
|
buf += 8;
|
|
}
|
|
else
|
|
{
|
|
/* Conditional jump.
|
|
JXX d16 -> JXX +1; JA +1; JAL d32 */
|
|
|
|
gas_assert (!RELAX_BRANCH_UNCOND (fragp->fr_subtype));
|
|
|
|
if (disp_is_known)
|
|
{
|
|
if (disp_to_target >= -32768 && disp_to_target <= 32767)
|
|
{
|
|
/* 16-bit disp is known and in range. Install a fixup
|
|
for the disp16 if the branch value is not constant.
|
|
This will be resolved by the assembler and units
|
|
converted. */
|
|
|
|
if (!RELAX_BRANCH_CONST (fragp->fr_subtype))
|
|
{
|
|
/* Install fixup for the branch. */
|
|
reloc_howto_type *reloc_howto
|
|
= bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
|
|
if (!reloc_howto)
|
|
abort();
|
|
|
|
fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
|
|
bfd_get_reloc_size (reloc_howto),
|
|
&exp,
|
|
reloc_howto->pc_relative,
|
|
BFD_RELOC_BPF_DISP16);
|
|
fixp->fx_file = fragp->fr_file;
|
|
fixp->fx_line = fragp->fr_line;
|
|
}
|
|
|
|
buf += 8;
|
|
}
|
|
else
|
|
{
|
|
/* 16-bit disp is known and not in range. Turn the JXX
|
|
into a sequence JXX +1; JA +1; JAL d32. */
|
|
|
|
char bytes[8];
|
|
|
|
/* First, set the 16-bit offset in the current
|
|
instruction to 1. */
|
|
|
|
if (target_big_endian)
|
|
bfd_putb16 (1, buf + 2);
|
|
else
|
|
bfd_putl16 (1, buf + 2);
|
|
buf += 8;
|
|
|
|
/* Then, write the JA + 1 */
|
|
|
|
bytes[0] = 0x05; /* JA */
|
|
bytes[1] = 0x0;
|
|
encode_int16 (1, bytes + 2);
|
|
bytes[4] = 0x0;
|
|
bytes[5] = 0x0;
|
|
bytes[6] = 0x0;
|
|
bytes[7] = 0x0;
|
|
write_insn_bytes (buf, bytes);
|
|
buf += 8;
|
|
|
|
/* Finally, write the JAL to the target. */
|
|
|
|
bytes[0] = ((BPF_CLASS_JMP32|BPF_CODE_JA|BPF_SRC_K) >> 56) & 0xff;
|
|
bytes[1] = 0;
|
|
bytes[2] = 0;
|
|
bytes[3] = 0;
|
|
encode_int32 ((int32_t) 0, bytes + 4);
|
|
write_insn_bytes (buf, bytes);
|
|
|
|
/* Install fixup for the JAL. */
|
|
reloc_howto_type *reloc_howto
|
|
= bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP32);
|
|
if (!reloc_howto)
|
|
abort();
|
|
|
|
fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
|
|
bfd_get_reloc_size (reloc_howto),
|
|
&exp,
|
|
reloc_howto->pc_relative,
|
|
BFD_RELOC_BPF_DISP32);
|
|
fixp->fx_file = fragp->fr_file;
|
|
fixp->fx_line = fragp->fr_line;
|
|
|
|
buf += 8;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* The displacement to the target is not known. Do not
|
|
relax. The linker will maybe do it if it chooses to. */
|
|
|
|
reloc_howto_type *reloc_howto = NULL;
|
|
|
|
gas_assert (!RELAX_BRANCH_CONST (fragp->fr_subtype));
|
|
|
|
/* Install fixup for the conditional jump. */
|
|
reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
|
|
if (!reloc_howto)
|
|
abort ();
|
|
|
|
fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
|
|
bfd_get_reloc_size (reloc_howto),
|
|
&exp,
|
|
reloc_howto->pc_relative,
|
|
BFD_RELOC_BPF_DISP16);
|
|
fixp->fx_file = fragp->fr_file;
|
|
fixp->fx_line = fragp->fr_line;
|
|
buf += 8;
|
|
}
|
|
}
|
|
|
|
gas_assert (buf == (bfd_byte *)fragp->fr_literal
|
|
+ fragp->fr_fix + fragp->fr_var);
|
|
|
|
fragp->fr_fix += fragp->fr_var;
|
|
}
|
|
|
|
|
|
/* Apply a fixS (fixup of an instruction or data that we didn't have
|
|
enough info to complete immediately) to the data in a frag. */
|
|
|
|
void
|
|
md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
|
|
{
|
|
char *where = fixP->fx_frag->fr_literal + fixP->fx_where;
|
|
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
case BFD_RELOC_BPF_DISP16:
|
|
/* Convert from bytes to number of 64-bit words to the target,
|
|
minus one. */
|
|
*valP = (((long) (*valP)) - 8) / 8;
|
|
break;
|
|
case BFD_RELOC_BPF_DISPCALL32:
|
|
case BFD_RELOC_BPF_DISP32:
|
|
/* Convert from bytes to number of 64-bit words to the target,
|
|
minus one. */
|
|
*valP = (((long) (*valP)) - 8) / 8;
|
|
|
|
if (fixP->fx_r_type == BFD_RELOC_BPF_DISPCALL32)
|
|
{
|
|
/* eBPF supports two kind of CALL instructions: the so
|
|
called pseudo calls ("bpf to bpf") and external calls
|
|
("bpf to kernel").
|
|
|
|
Both kind of calls use the same instruction (CALL).
|
|
However, external calls are constructed by passing a
|
|
constant argument to the instruction, whereas pseudo
|
|
calls result from expressions involving symbols. In
|
|
practice, instructions requiring a fixup are interpreted
|
|
as pseudo-calls. If we are executing this code, this is
|
|
a pseudo call.
|
|
|
|
The kernel expects for pseudo-calls to be annotated by
|
|
having BPF_PSEUDO_CALL in the SRC field of the
|
|
instruction. But beware the infamous nibble-swapping of
|
|
eBPF and take endianness into account here.
|
|
|
|
Note that the CALL instruction has only one operand, so
|
|
this code is executed only once per instruction. */
|
|
md_number_to_chars (where + 1, target_big_endian ? 0x01 : 0x10, 1);
|
|
}
|
|
break;
|
|
case BFD_RELOC_16_PCREL:
|
|
/* Convert from bytes to number of 64-bit words to the target,
|
|
minus one. */
|
|
*valP = (((long) (*valP)) - 8) / 8;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (fixP->fx_addsy == (symbolS *) NULL)
|
|
fixP->fx_done = 1;
|
|
|
|
if (fixP->fx_done)
|
|
{
|
|
/* We're finished with this fixup. Install it because
|
|
bfd_install_relocation won't be called to do it. */
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
case BFD_RELOC_8:
|
|
md_number_to_chars (where, *valP, 1);
|
|
break;
|
|
case BFD_RELOC_16:
|
|
md_number_to_chars (where, *valP, 2);
|
|
break;
|
|
case BFD_RELOC_32:
|
|
md_number_to_chars (where, *valP, 4);
|
|
break;
|
|
case BFD_RELOC_64:
|
|
md_number_to_chars (where, *valP, 8);
|
|
break;
|
|
case BFD_RELOC_BPF_DISP16:
|
|
md_number_to_chars (where + 2, (uint16_t) *valP, 2);
|
|
break;
|
|
case BFD_RELOC_BPF_DISP32:
|
|
case BFD_RELOC_BPF_DISPCALL32:
|
|
md_number_to_chars (where + 4, (uint32_t) *valP, 4);
|
|
break;
|
|
case BFD_RELOC_16_PCREL:
|
|
md_number_to_chars (where + 2, (uint32_t) *valP, 2);
|
|
break;
|
|
default:
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("internal error: can't install fix for reloc type %d (`%s')"),
|
|
fixP->fx_r_type, bfd_get_reloc_code_name (fixP->fx_r_type));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Tuck `value' away for use by tc_gen_reloc.
|
|
See the comment describing fx_addnumber in write.h.
|
|
This field is misnamed (or misused :-). */
|
|
fixP->fx_addnumber = *valP;
|
|
}
|
|
|
|
|
|
/* Instruction writing routines. */
|
|
|
|
/* Encode a BPF instruction in the given buffer BYTES. Non-constant
|
|
immediates are encoded as zeroes. */
|
|
|
|
static void
|
|
encode_insn (struct bpf_insn *insn, char *bytes,
|
|
int relaxed ATTRIBUTE_UNUSED)
|
|
{
|
|
uint8_t src, dst;
|
|
|
|
/* Zero all the bytes. */
|
|
memset (bytes, 0, 16);
|
|
|
|
/* First encode the opcodes. Note that we have to handle the
|
|
endianness groups of the BPF instructions: 8 | 4 | 4 | 16 |
|
|
32. */
|
|
if (target_big_endian)
|
|
{
|
|
/* code */
|
|
bytes[0] = (insn->opcode >> 56) & 0xff;
|
|
/* regs */
|
|
bytes[1] = (insn->opcode >> 48) & 0xff;
|
|
/* offset16 */
|
|
bytes[2] = (insn->opcode >> 40) & 0xff;
|
|
bytes[3] = (insn->opcode >> 32) & 0xff;
|
|
/* imm32 */
|
|
bytes[4] = (insn->opcode >> 24) & 0xff;
|
|
bytes[5] = (insn->opcode >> 16) & 0xff;
|
|
bytes[6] = (insn->opcode >> 8) & 0xff;
|
|
bytes[7] = insn->opcode & 0xff;
|
|
}
|
|
else
|
|
{
|
|
/* code */
|
|
bytes[0] = (insn->opcode >> 56) & 0xff;
|
|
/* regs */
|
|
bytes[1] = (((((insn->opcode >> 48) & 0xff) & 0xf) << 4)
|
|
| (((insn->opcode >> 48) & 0xff) & 0xf));
|
|
/* offset16 */
|
|
bytes[3] = (insn->opcode >> 40) & 0xff;
|
|
bytes[2] = (insn->opcode >> 32) & 0xff;
|
|
/* imm32 */
|
|
bytes[7] = (insn->opcode >> 24) & 0xff;
|
|
bytes[6] = (insn->opcode >> 16) & 0xff;
|
|
bytes[5] = (insn->opcode >> 8) & 0xff;
|
|
bytes[4] = insn->opcode & 0xff;
|
|
}
|
|
|
|
/* Now the registers. */
|
|
src = insn->has_src ? insn->src : 0;
|
|
dst = insn->has_dst ? insn->dst : 0;
|
|
|
|
if (target_big_endian)
|
|
bytes[1] = ((dst & 0xf) << 4) | (src & 0xf);
|
|
else
|
|
bytes[1] = ((src & 0xf) << 4) | (dst & 0xf);
|
|
|
|
/* Now the immediates that are known to be constant. */
|
|
|
|
if (insn->has_imm32 && insn->imm32.X_op == O_constant)
|
|
{
|
|
int64_t imm = insn->imm32.X_add_number;
|
|
|
|
if (immediate_overflow (imm, 32))
|
|
as_bad (_("immediate out of range, shall fit in 32 bits"));
|
|
else
|
|
encode_int32 (insn->imm32.X_add_number, bytes + 4);
|
|
}
|
|
|
|
if (insn->has_disp32 && insn->disp32.X_op == O_constant)
|
|
{
|
|
int64_t disp = insn->disp32.X_add_number;
|
|
|
|
if (immediate_overflow (disp, 32))
|
|
as_bad (_("pc-relative offset out of range, shall fit in 32 bits"));
|
|
else
|
|
encode_int32 (insn->disp32.X_add_number, bytes + 4);
|
|
}
|
|
|
|
if (insn->has_offset16 && insn->offset16.X_op == O_constant)
|
|
{
|
|
int64_t offset = insn->offset16.X_add_number;
|
|
|
|
if (immediate_overflow (offset, 16))
|
|
as_bad (_("pc-relative offset out of range, shall fit in 16 bits"));
|
|
else
|
|
encode_int16 (insn->offset16.X_add_number, bytes + 2);
|
|
}
|
|
|
|
if (insn->has_disp16 && insn->disp16.X_op == O_constant)
|
|
{
|
|
int64_t disp = insn->disp16.X_add_number;
|
|
|
|
if (immediate_overflow (disp, 16))
|
|
as_bad (_("pc-relative offset out of range, shall fit in 16 bits"));
|
|
else
|
|
encode_int16 (insn->disp16.X_add_number, bytes + 2);
|
|
}
|
|
|
|
if (insn->has_imm64 && insn->imm64.X_op == O_constant)
|
|
{
|
|
uint64_t imm64 = insn->imm64.X_add_number;
|
|
|
|
if (target_big_endian)
|
|
{
|
|
bytes[12] = (imm64 >> 56) & 0xff;
|
|
bytes[13] = (imm64 >> 48) & 0xff;
|
|
bytes[14] = (imm64 >> 40) & 0xff;
|
|
bytes[15] = (imm64 >> 32) & 0xff;
|
|
bytes[4] = (imm64 >> 24) & 0xff;
|
|
bytes[5] = (imm64 >> 16) & 0xff;
|
|
bytes[6] = (imm64 >> 8) & 0xff;
|
|
bytes[7] = imm64 & 0xff;
|
|
}
|
|
else
|
|
{
|
|
bytes[15] = (imm64 >> 56) & 0xff;
|
|
bytes[14] = (imm64 >> 48) & 0xff;
|
|
bytes[13] = (imm64 >> 40) & 0xff;
|
|
bytes[12] = (imm64 >> 32) & 0xff;
|
|
bytes[7] = (imm64 >> 24) & 0xff;
|
|
bytes[6] = (imm64 >> 16) & 0xff;
|
|
bytes[5] = (imm64 >> 8) & 0xff;
|
|
bytes[4] = imm64 & 0xff;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Install the fixups in INSN in their proper location in the
|
|
specified FRAG at the location pointed by WHERE. */
|
|
|
|
static void
|
|
install_insn_fixups (struct bpf_insn *insn, fragS *frag, long where)
|
|
{
|
|
if (insn->has_imm64)
|
|
{
|
|
switch (insn->imm64.X_op)
|
|
{
|
|
case O_symbol:
|
|
case O_subtract:
|
|
case O_add:
|
|
{
|
|
reloc_howto_type *reloc_howto;
|
|
int size;
|
|
|
|
reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_64);
|
|
if (!reloc_howto)
|
|
abort ();
|
|
|
|
size = bfd_get_reloc_size (reloc_howto);
|
|
|
|
fix_new_exp (frag, where,
|
|
size, &insn->imm64, reloc_howto->pc_relative,
|
|
BFD_RELOC_BPF_64);
|
|
break;
|
|
}
|
|
case O_constant:
|
|
/* Already handled in encode_insn. */
|
|
break;
|
|
default:
|
|
abort ();
|
|
}
|
|
}
|
|
|
|
if (insn->has_imm32)
|
|
{
|
|
switch (insn->imm32.X_op)
|
|
{
|
|
case O_symbol:
|
|
case O_subtract:
|
|
case O_add:
|
|
case O_uminus:
|
|
{
|
|
reloc_howto_type *reloc_howto;
|
|
int size;
|
|
|
|
reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_32);
|
|
if (!reloc_howto)
|
|
abort ();
|
|
|
|
size = bfd_get_reloc_size (reloc_howto);
|
|
|
|
fix_new_exp (frag, where + 4,
|
|
size, &insn->imm32, reloc_howto->pc_relative,
|
|
BFD_RELOC_32);
|
|
break;
|
|
}
|
|
case O_constant:
|
|
/* Already handled in encode_insn. */
|
|
break;
|
|
default:
|
|
abort ();
|
|
}
|
|
}
|
|
|
|
if (insn->has_disp32)
|
|
{
|
|
switch (insn->disp32.X_op)
|
|
{
|
|
case O_symbol:
|
|
case O_subtract:
|
|
case O_add:
|
|
{
|
|
reloc_howto_type *reloc_howto;
|
|
int size;
|
|
unsigned int bfd_reloc
|
|
= (insn->id == BPF_INSN_CALL
|
|
? BFD_RELOC_BPF_DISPCALL32
|
|
: BFD_RELOC_BPF_DISP32);
|
|
|
|
reloc_howto = bfd_reloc_type_lookup (stdoutput, bfd_reloc);
|
|
if (!reloc_howto)
|
|
abort ();
|
|
|
|
size = bfd_get_reloc_size (reloc_howto);
|
|
|
|
fix_new_exp (frag, where,
|
|
size, &insn->disp32, reloc_howto->pc_relative,
|
|
bfd_reloc);
|
|
break;
|
|
}
|
|
case O_constant:
|
|
/* Already handled in encode_insn. */
|
|
break;
|
|
default:
|
|
abort ();
|
|
}
|
|
}
|
|
|
|
if (insn->has_offset16)
|
|
{
|
|
switch (insn->offset16.X_op)
|
|
{
|
|
case O_symbol:
|
|
case O_subtract:
|
|
case O_add:
|
|
{
|
|
reloc_howto_type *reloc_howto;
|
|
int size;
|
|
|
|
/* XXX we really need a new pc-rel offset in bytes
|
|
relocation for this. */
|
|
reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
|
|
if (!reloc_howto)
|
|
abort ();
|
|
|
|
size = bfd_get_reloc_size (reloc_howto);
|
|
|
|
fix_new_exp (frag, where,
|
|
size, &insn->offset16, reloc_howto->pc_relative,
|
|
BFD_RELOC_BPF_DISP16);
|
|
break;
|
|
}
|
|
case O_constant:
|
|
/* Already handled in encode_insn. */
|
|
break;
|
|
default:
|
|
abort ();
|
|
}
|
|
}
|
|
|
|
if (insn->has_disp16)
|
|
{
|
|
switch (insn->disp16.X_op)
|
|
{
|
|
case O_symbol:
|
|
case O_subtract:
|
|
case O_add:
|
|
{
|
|
reloc_howto_type *reloc_howto;
|
|
int size;
|
|
|
|
reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
|
|
if (!reloc_howto)
|
|
abort ();
|
|
|
|
size = bfd_get_reloc_size (reloc_howto);
|
|
|
|
fix_new_exp (frag, where,
|
|
size, &insn->disp16, reloc_howto->pc_relative,
|
|
BFD_RELOC_BPF_DISP16);
|
|
break;
|
|
}
|
|
case O_constant:
|
|
/* Already handled in encode_insn. */
|
|
break;
|
|
default:
|
|
abort ();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* Add a new insn to the list of instructions. */
|
|
|
|
static void
|
|
add_fixed_insn (struct bpf_insn *insn)
|
|
{
|
|
char *this_frag = frag_more (insn->size);
|
|
char bytes[16];
|
|
int i;
|
|
|
|
/* First encode the known parts of the instruction, including
|
|
opcodes and constant immediates, and write them to the frag. */
|
|
encode_insn (insn, bytes, 0 /* relax */);
|
|
for (i = 0; i < insn->size; ++i)
|
|
md_number_to_chars (this_frag + i, (valueT) bytes[i], 1);
|
|
|
|
/* Now install the instruction fixups. */
|
|
install_insn_fixups (insn, frag_now,
|
|
this_frag - frag_now->fr_literal);
|
|
}
|
|
|
|
/* Add a new relaxable to the list of instructions. */
|
|
|
|
static void
|
|
add_relaxed_insn (struct bpf_insn *insn, expressionS *exp)
|
|
{
|
|
char bytes[16];
|
|
int i;
|
|
char *this_frag;
|
|
unsigned worst_case = relaxed_branch_length (NULL, NULL, 0);
|
|
unsigned best_case = insn->size;
|
|
|
|
/* We only support relaxing branches, for the moment. */
|
|
relax_substateT subtype
|
|
= RELAX_BRANCH_ENCODE (insn->id == BPF_INSN_JAR,
|
|
exp->X_op == O_constant,
|
|
worst_case);
|
|
|
|
frag_grow (worst_case);
|
|
this_frag = frag_more (0);
|
|
|
|
/* First encode the known parts of the instruction, including
|
|
opcodes and constant immediates, and write them to the frag. */
|
|
encode_insn (insn, bytes, 1 /* relax */);
|
|
for (i = 0; i < insn->size; ++i)
|
|
md_number_to_chars (this_frag + i, (valueT) bytes[i], 1);
|
|
|
|
/* Note that instruction fixups will be applied once the frag is
|
|
relaxed, in md_convert_frag. */
|
|
frag_var (rs_machine_dependent,
|
|
worst_case, best_case,
|
|
subtype, exp->X_add_symbol, exp->X_add_number /* offset */,
|
|
NULL);
|
|
}
|
|
|
|
|
|
/* Parse an operand expression. Returns the first character that is
|
|
not part of the expression, or NULL in case of parse error.
|
|
|
|
See md_operand below to see how exp_parse_failed is used. */
|
|
|
|
static int exp_parse_failed = 0;
|
|
static bool parsing_insn_operands = false;
|
|
|
|
static char *
|
|
parse_expression (char *s, expressionS *exp)
|
|
{
|
|
char *saved_input_line_pointer = input_line_pointer;
|
|
char *saved_s = s;
|
|
|
|
/* Wake up bpf_parse_name before the call to expression (). */
|
|
parsing_insn_operands = true;
|
|
|
|
exp_parse_failed = 0;
|
|
input_line_pointer = s;
|
|
expression (exp);
|
|
s = input_line_pointer;
|
|
input_line_pointer = saved_input_line_pointer;
|
|
|
|
if (exp->X_op == O_absent || exp_parse_failed)
|
|
return NULL;
|
|
|
|
/* The expression parser may consume trailing whitespaces. We have
|
|
to undo that since the instruction templates may be expecting
|
|
these whitespaces. */
|
|
{
|
|
char *p;
|
|
for (p = s - 1; p >= saved_s && *p == ' '; --p)
|
|
--s;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
/* Parse a BPF register name and return the corresponding register
|
|
number. Return NULL in case of parse error, or a pointer to the
|
|
first character in S that is not part of the register name. */
|
|
|
|
static char *
|
|
parse_bpf_register (char *s, char rw, uint8_t *regno)
|
|
{
|
|
if (asm_dialect == DIALECT_NORMAL)
|
|
{
|
|
rw = 'r';
|
|
if (*s != '%')
|
|
return NULL;
|
|
s += 1;
|
|
|
|
if (*s == 'f' && *(s + 1) == 'p')
|
|
{
|
|
*regno = 10;
|
|
s += 2;
|
|
return s;
|
|
}
|
|
}
|
|
|
|
if (*s != rw)
|
|
return NULL;
|
|
s += 1;
|
|
|
|
if (*s == '1')
|
|
{
|
|
if (*(s + 1) == '0')
|
|
{
|
|
*regno = 10;
|
|
s += 2;
|
|
}
|
|
else
|
|
{
|
|
*regno = 1;
|
|
s += 1;
|
|
}
|
|
}
|
|
else if (*s >= '0' && *s <= '9')
|
|
{
|
|
*regno = *s - '0';
|
|
s += 1;
|
|
}
|
|
|
|
/* If we are still parsing a name, it is not a register. */
|
|
if (is_part_of_name (*s))
|
|
return NULL;
|
|
|
|
return s;
|
|
}
|
|
|
|
/* Symbols created by this parse, but not yet committed to the real
|
|
symbol table. */
|
|
static symbolS *deferred_sym_rootP;
|
|
static symbolS *deferred_sym_lastP;
|
|
|
|
/* Symbols discarded by a previous parse. Symbols cannot easily be freed
|
|
after creation, so try to recycle. */
|
|
static symbolS *orphan_sym_rootP;
|
|
static symbolS *orphan_sym_lastP;
|
|
|
|
/* Implement md_parse_name hook. Handles any symbol found in an expression.
|
|
This allows us to tentatively create symbols, before we know for sure
|
|
whether the parser is using the correct template for an instruction.
|
|
If we end up keeping the instruction, the deferred symbols are committed
|
|
to the real symbol table. This approach is modeled after the riscv port. */
|
|
|
|
bool
|
|
bpf_parse_name (const char *name, expressionS *exp, enum expr_mode mode)
|
|
{
|
|
symbolS *sym;
|
|
|
|
/* If we aren't currently parsing an instruction, don't do anything.
|
|
This prevents tampering with operands to directives. */
|
|
if (!parsing_insn_operands)
|
|
return false;
|
|
|
|
gas_assert (mode == expr_normal);
|
|
|
|
/* Pseudo-C syntax uses unprefixed register names like r2 or w3.
|
|
Since many instructions take either a register or an
|
|
immediate/expression, we should not allow references to symbols
|
|
with these names in operands. */
|
|
if (asm_dialect == DIALECT_PSEUDOC)
|
|
{
|
|
uint8_t regno;
|
|
|
|
if (parse_bpf_register ((char *) name, 'r', ®no)
|
|
|| parse_bpf_register ((char *) name, 'w', ®no))
|
|
{
|
|
as_bad (_("unexpected register name `%s' in expression"),
|
|
name);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (symbol_find (name) != NULL)
|
|
return false;
|
|
|
|
for (sym = deferred_sym_rootP; sym; sym = symbol_next (sym))
|
|
if (strcmp (name, S_GET_NAME (sym)) == 0)
|
|
break;
|
|
|
|
/* Tentatively create a symbol. */
|
|
if (!sym)
|
|
{
|
|
/* See if we can reuse a symbol discarded by a previous parse.
|
|
This may be quite common, for example when trying multiple templates
|
|
for an instruction with the first reference to a valid symbol. */
|
|
for (sym = orphan_sym_rootP; sym; sym = symbol_next (sym))
|
|
if (strcmp (name, S_GET_NAME (sym)) == 0)
|
|
{
|
|
symbol_remove (sym, &orphan_sym_rootP, &orphan_sym_lastP);
|
|
break;
|
|
}
|
|
|
|
if (!sym)
|
|
sym = symbol_create (name, undefined_section, &zero_address_frag, 0);
|
|
|
|
/* Add symbol to the deferred list. If we commit to the isntruction,
|
|
then the symbol will be inserted into to the real symbol table at
|
|
that point (in md_assemble). */
|
|
symbol_append (sym, deferred_sym_lastP, &deferred_sym_rootP,
|
|
&deferred_sym_lastP);
|
|
}
|
|
|
|
exp->X_op = O_symbol;
|
|
exp->X_add_symbol = sym;
|
|
exp->X_add_number = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Collect a parse error message. */
|
|
|
|
static int partial_match_length = 0;
|
|
static char *errmsg = NULL;
|
|
|
|
static void
|
|
parse_error (int length, const char *fmt, ...)
|
|
{
|
|
if (length > partial_match_length)
|
|
{
|
|
va_list args;
|
|
|
|
free (errmsg);
|
|
va_start (args, fmt);
|
|
errmsg = xvasprintf (fmt, args);
|
|
va_end (args);
|
|
partial_match_length = length;
|
|
}
|
|
|
|
/* Discard deferred symbols from the failed parse. They may potentially
|
|
be reused in the future from the orphan list. */
|
|
while (deferred_sym_rootP)
|
|
{
|
|
symbolS *sym = deferred_sym_rootP;
|
|
symbol_remove (sym, &deferred_sym_rootP, &deferred_sym_lastP);
|
|
symbol_append (sym, orphan_sym_lastP, &orphan_sym_rootP,
|
|
&orphan_sym_lastP);
|
|
}
|
|
}
|
|
|
|
/* Assemble a machine instruction in STR and emit the frags/bytes it
|
|
assembles to. */
|
|
|
|
void
|
|
md_assemble (char *str ATTRIBUTE_UNUSED)
|
|
{
|
|
/* There are two different syntaxes that can be used to write BPF
|
|
instructions. One is very conventional and like any other
|
|
assembly language where each instruction is conformed by an
|
|
instruction mnemonic followed by its operands. This is what we
|
|
call the "normal" syntax. The other syntax tries to look like C
|
|
statements. We have to support both syntaxes in this assembler.
|
|
|
|
One of the many nuisances introduced by this eccentricity is that
|
|
in the pseudo-c syntax it is not possible to hash the opcodes
|
|
table by instruction mnemonic, because there is none. So we have
|
|
no other choice than to try to parse all instruction opcodes
|
|
until one matches. This is slow.
|
|
|
|
Another problem is that emitting detailed diagnostics becomes
|
|
tricky, since the lack of mnemonic means it is not clear what
|
|
instruction was intended by the user, and we cannot emit
|
|
diagnostics for every attempted template. So if an instruction
|
|
is not parsed, we report the diagnostic corresponding to the
|
|
partially parsed instruction that was matched further. */
|
|
|
|
unsigned int idx = 0;
|
|
struct bpf_insn insn;
|
|
const struct bpf_opcode *opcode;
|
|
|
|
/* Initialize the global diagnostic variables. See the parse_error
|
|
function above. */
|
|
partial_match_length = 0;
|
|
errmsg = NULL;
|
|
|
|
#define PARSE_ERROR(...) parse_error (s > str ? s - str : 0, __VA_ARGS__)
|
|
|
|
while ((opcode = bpf_get_opcode (idx++)) != NULL)
|
|
{
|
|
const char *p;
|
|
char *s;
|
|
const char *template
|
|
= (asm_dialect == DIALECT_PSEUDOC ? opcode->pseudoc : opcode->normal);
|
|
|
|
/* Do not try to match opcodes with a higher version than the
|
|
selected ISA spec. */
|
|
if (opcode->version > isa_spec)
|
|
continue;
|
|
|
|
memset (&insn, 0, sizeof (struct bpf_insn));
|
|
insn.size = 8;
|
|
for (s = str, p = template; *p != '\0';)
|
|
{
|
|
if (*p == ' ')
|
|
{
|
|
/* Expect zero or more spaces. */
|
|
while (*s != '\0' && (*s == ' ' || *s == '\t'))
|
|
s += 1;
|
|
p += 1;
|
|
}
|
|
else if (*p == '%')
|
|
{
|
|
if (*(p + 1) == '%')
|
|
{
|
|
if (*s != '%')
|
|
{
|
|
PARSE_ERROR ("expected '%%'");
|
|
break;
|
|
}
|
|
p += 2;
|
|
s += 1;
|
|
}
|
|
else if (*(p + 1) == 'w')
|
|
{
|
|
/* Expect zero or more spaces. */
|
|
while (*s != '\0' && (*s == ' ' || *s == '\t'))
|
|
s += 1;
|
|
p += 2;
|
|
}
|
|
else if (*(p + 1) == 'W')
|
|
{
|
|
/* Expect one or more spaces. */
|
|
if (*s != ' ' && *s != '\t')
|
|
{
|
|
PARSE_ERROR ("expected white space, got '%s'",
|
|
s);
|
|
break;
|
|
}
|
|
while (*s != '\0' && (*s == ' ' || *s == '\t'))
|
|
s += 1;
|
|
p += 2;
|
|
}
|
|
else if (strncmp (p, "%dr", 3) == 0)
|
|
{
|
|
uint8_t regno;
|
|
char *news = parse_bpf_register (s, 'r', ®no);
|
|
|
|
if (news == NULL || (insn.has_dst && regno != insn.dst))
|
|
{
|
|
if (news != NULL)
|
|
PARSE_ERROR ("expected register r%d, got r%d",
|
|
insn.dst, regno);
|
|
else
|
|
PARSE_ERROR ("expected register name, got '%s'", s);
|
|
break;
|
|
}
|
|
s = news;
|
|
insn.dst = regno;
|
|
insn.has_dst = 1;
|
|
p += 3;
|
|
}
|
|
else if (strncmp (p, "%sr", 3) == 0)
|
|
{
|
|
uint8_t regno;
|
|
char *news = parse_bpf_register (s, 'r', ®no);
|
|
|
|
if (news == NULL || (insn.has_src && regno != insn.src))
|
|
{
|
|
if (news != NULL)
|
|
PARSE_ERROR ("expected register r%d, got r%d",
|
|
insn.dst, regno);
|
|
else
|
|
PARSE_ERROR ("expected register name, got '%s'", s);
|
|
break;
|
|
}
|
|
s = news;
|
|
insn.src = regno;
|
|
insn.has_src = 1;
|
|
p += 3;
|
|
}
|
|
else if (strncmp (p, "%dw", 3) == 0)
|
|
{
|
|
uint8_t regno;
|
|
char *news = parse_bpf_register (s, 'w', ®no);
|
|
|
|
if (news == NULL || (insn.has_dst && regno != insn.dst))
|
|
{
|
|
if (news != NULL)
|
|
PARSE_ERROR ("expected register r%d, got r%d",
|
|
insn.dst, regno);
|
|
else
|
|
PARSE_ERROR ("expected register name, got '%s'", s);
|
|
break;
|
|
}
|
|
s = news;
|
|
insn.dst = regno;
|
|
insn.has_dst = 1;
|
|
p += 3;
|
|
}
|
|
else if (strncmp (p, "%sw", 3) == 0)
|
|
{
|
|
uint8_t regno;
|
|
char *news = parse_bpf_register (s, 'w', ®no);
|
|
|
|
if (news == NULL || (insn.has_src && regno != insn.src))
|
|
{
|
|
if (news != NULL)
|
|
PARSE_ERROR ("expected register r%d, got r%d",
|
|
insn.dst, regno);
|
|
else
|
|
PARSE_ERROR ("expected register name, got '%s'", s);
|
|
break;
|
|
}
|
|
s = news;
|
|
insn.src = regno;
|
|
insn.has_src = 1;
|
|
p += 3;
|
|
}
|
|
else if (strncmp (p, "%i32", 4) == 0
|
|
|| strncmp (p, "%I32", 4) == 0)
|
|
{
|
|
char *exp = NULL;
|
|
|
|
if (p[1] == 'I')
|
|
{
|
|
while (*s == ' ' || *s == '\t')
|
|
s += 1;
|
|
if (*s != '+' && *s != '-')
|
|
{
|
|
PARSE_ERROR ("expected `+' or `-', got `%c'", *s);
|
|
break;
|
|
}
|
|
}
|
|
|
|
exp = parse_expression (s, &insn.imm32);
|
|
if (exp == NULL)
|
|
{
|
|
PARSE_ERROR ("expected signed 32-bit immediate");
|
|
break;
|
|
}
|
|
s = exp;
|
|
insn.has_imm32 = 1;
|
|
p += 4;
|
|
}
|
|
else if (strncmp (p, "%o16", 4) == 0)
|
|
{
|
|
char *exp = NULL;
|
|
|
|
while (*s == ' ' || *s == '\t')
|
|
s += 1;
|
|
if (*s != '+' && *s != '-')
|
|
{
|
|
PARSE_ERROR ("expected `+' or `-', got `%c'", *s);
|
|
break;
|
|
}
|
|
|
|
exp = parse_expression (s, &insn.offset16);
|
|
if (exp == NULL)
|
|
{
|
|
PARSE_ERROR ("expected signed 16-bit offset");
|
|
break;
|
|
}
|
|
s = exp;
|
|
insn.has_offset16 = 1;
|
|
p += 4;
|
|
}
|
|
else if (strncmp (p, "%d16", 4) == 0)
|
|
{
|
|
char *exp = parse_expression (s, &insn.disp16);
|
|
|
|
if (exp == NULL)
|
|
{
|
|
PARSE_ERROR ("expected signed 16-bit displacement");
|
|
break;
|
|
}
|
|
s = exp;
|
|
insn.has_disp16 = 1;
|
|
insn.is_relaxable = (insn.disp16.X_op != O_constant);
|
|
p += 4;
|
|
}
|
|
else if (strncmp (p, "%d32", 4) == 0)
|
|
{
|
|
char *exp = parse_expression (s, &insn.disp32);
|
|
|
|
if (exp == NULL)
|
|
{
|
|
PARSE_ERROR ("expected signed 32-bit displacement");
|
|
break;
|
|
}
|
|
s = exp;
|
|
insn.has_disp32 = 1;
|
|
p += 4;
|
|
}
|
|
else if (strncmp (p, "%i64", 4) == 0)
|
|
{
|
|
char *exp = parse_expression (s, &insn.imm64);
|
|
|
|
if (exp == NULL)
|
|
{
|
|
PARSE_ERROR ("expected signed 64-bit immediate");
|
|
break;
|
|
}
|
|
s = exp;
|
|
insn.has_imm64 = 1;
|
|
insn.size = 16;
|
|
p += 4;
|
|
}
|
|
else
|
|
as_fatal (_("invalid %%-tag in BPF opcode '%s'\n"), template);
|
|
}
|
|
else
|
|
{
|
|
/* Match a literal character. */
|
|
if (*s != *p)
|
|
{
|
|
if (*s == '\0')
|
|
PARSE_ERROR ("expected '%c'", *p);
|
|
else if (*s == '%')
|
|
{
|
|
/* This is to workaround a bug in as_bad. */
|
|
char tmp[3];
|
|
|
|
tmp[0] = '%';
|
|
tmp[1] = '%';
|
|
tmp[2] = '\0';
|
|
|
|
PARSE_ERROR ("expected '%c', got '%s'", *p, tmp);
|
|
}
|
|
else
|
|
PARSE_ERROR ("expected '%c', got '%c'", *p, *s);
|
|
break;
|
|
}
|
|
p += 1;
|
|
s += 1;
|
|
}
|
|
}
|
|
|
|
if (*p == '\0')
|
|
{
|
|
/* Allow white spaces at the end of the line. */
|
|
while (*s != '\0' && (*s == ' ' || *s == '\t'))
|
|
s += 1;
|
|
if (*s == '\0')
|
|
/* We parsed an instruction successfully. */
|
|
break;
|
|
PARSE_ERROR ("extra junk at end of line");
|
|
}
|
|
}
|
|
|
|
/* Mark that we are no longer parsing an instruction, bpf_parse_name does
|
|
not interfere with symbols in e.g. assembler directives. */
|
|
parsing_insn_operands = false;
|
|
|
|
if (opcode == NULL)
|
|
{
|
|
as_bad (_("unrecognized instruction `%s'"), str);
|
|
if (errmsg != NULL)
|
|
{
|
|
as_bad ("%s", errmsg);
|
|
free (errmsg);
|
|
errmsg = NULL;
|
|
}
|
|
|
|
return;
|
|
}
|
|
insn.id = opcode->id;
|
|
insn.opcode = opcode->opcode;
|
|
|
|
#undef PARSE_ERROR
|
|
|
|
/* Commit any symbols created while parsing the instruction. */
|
|
while (deferred_sym_rootP)
|
|
{
|
|
symbolS *sym = deferred_sym_rootP;
|
|
symbol_remove (sym, &deferred_sym_rootP, &deferred_sym_lastP);
|
|
symbol_append (sym, symbol_lastP, &symbol_rootP, &symbol_lastP);
|
|
symbol_table_insert (sym);
|
|
}
|
|
|
|
/* Generate the frags and fixups for the parsed instruction. */
|
|
if (do_relax && isa_spec >= BPF_V4 && insn.is_relaxable)
|
|
{
|
|
expressionS *relaxable_exp = NULL;
|
|
|
|
if (insn.has_disp16)
|
|
relaxable_exp = &insn.disp16;
|
|
else
|
|
abort ();
|
|
|
|
add_relaxed_insn (&insn, relaxable_exp);
|
|
}
|
|
else
|
|
add_fixed_insn (&insn);
|
|
|
|
/* Emit DWARF2 debugging information. */
|
|
dwarf2_emit_insn (insn.size);
|
|
}
|
|
|
|
/* Parse an operand that is machine-specific. */
|
|
|
|
void
|
|
md_operand (expressionS *expressionP)
|
|
{
|
|
/* If this hook is invoked it means GAS failed to parse a generic
|
|
expression. We should inhibit the as_bad in expr.c, so we can
|
|
fail while parsing instruction alternatives. To do that, we
|
|
change the expression to not have an O_absent. But then we also
|
|
need to set exp_parse_failed to parse_expression above does the
|
|
right thing. */
|
|
++input_line_pointer;
|
|
expressionP->X_op = O_constant;
|
|
expressionP->X_add_number = 0;
|
|
exp_parse_failed = 1;
|
|
}
|
|
|
|
symbolS *
|
|
md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Turn a string in input_line_pointer into a floating point constant
|
|
of type TYPE, and store the appropriate bytes in *LITP. The number
|
|
of LITTLENUMS emitted is stored in *SIZEP. An error message is
|
|
returned, or NULL on OK. */
|
|
|
|
const char *
|
|
md_atof (int type, char *litP, int *sizeP)
|
|
{
|
|
return ieee_md_atof (type, litP, sizeP, false);
|
|
}
|
|
|
|
|
|
/* Determine whether the equal sign in the given string corresponds to
|
|
a BPF instruction, i.e. when it is not to be considered a symbol
|
|
assignment. */
|
|
|
|
bool
|
|
bpf_tc_equal_in_insn (int c ATTRIBUTE_UNUSED, char *str ATTRIBUTE_UNUSED)
|
|
{
|
|
uint8_t regno;
|
|
|
|
/* Only pseudo-c instructions can have equal signs, and of these,
|
|
all that could be confused with a symbol assignment all start
|
|
with a register name. */
|
|
if (asm_dialect == DIALECT_PSEUDOC)
|
|
{
|
|
char *w = parse_bpf_register (str, 'w', ®no);
|
|
char *r = parse_bpf_register (str, 'r', ®no);
|
|
|
|
if ((w != NULL && *w == '\0')
|
|
|| (r != NULL && *r == '\0'))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Some special processing for a BPF ELF file. */
|
|
|
|
void
|
|
bpf_elf_final_processing (void)
|
|
{
|
|
/* Annotate the BPF ISA version in the ELF flag bits. */
|
|
elf_elfheader (stdoutput)->e_flags |= (isa_spec & EF_BPF_CPUVER);
|
|
}
|