| /* 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); |
| } |