/* tc-microblaze.c -- Assemble code for Xilinx MicroBlaze Copyright 2009, 2010, 2012 Free Software Foundation. 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 #include "as.h" #include "bfd.h" #include "subsegs.h" #define DEFINE_TABLE #include "../opcodes/microblaze-opc.h" #include "../opcodes/microblaze-opcm.h" #include "safe-ctype.h" #include #include #include "aout/stab_gnu.h" #ifndef streq #define streq(a,b) (strcmp (a, b) == 0) #endif void microblaze_generate_symbol (char *sym); static bfd_boolean check_spl_reg (unsigned *); /* Several places in this file insert raw instructions into the object. They should generate the instruction and then use these four macros to crack the instruction value into the appropriate byte values. */ #define INST_BYTE0(x) (target_big_endian ? (((x) >> 24) & 0xFF) : ((x) & 0xFF)) #define INST_BYTE1(x) (target_big_endian ? (((x) >> 16) & 0xFF) : (((x) >> 8) & 0xFF)) #define INST_BYTE2(x) (target_big_endian ? (((x) >> 8) & 0xFF) : (((x) >> 16) & 0xFF)) #define INST_BYTE3(x) (target_big_endian ? ((x) & 0xFF) : (((x) >> 24) & 0xFF)) /* This array holds the chars that always start a comment. If the pre-processor is disabled, these aren't very useful. */ const char comment_chars[] = "#"; const char line_separator_chars[] = ";"; /* This array holds the chars that only start a comment at the beginning of a line. */ const char line_comment_chars[] = "#"; const int md_reloc_size = 8; /* Size of relocation record. */ /* Chars that can be used to separate mant from exp in floating point numbers. */ const char EXP_CHARS[] = "eE"; /* Chars that mean this number is a floating point constant As in 0f12.456 or 0d1.2345e12. */ const char FLT_CHARS[] = "rRsSfFdDxXpP"; /* INST_PC_OFFSET and INST_NO_OFFSET are 0 and 1. */ #define UNDEFINED_PC_OFFSET 2 #define DEFINED_ABS_SEGMENT 3 #define DEFINED_PC_OFFSET 4 #define DEFINED_RO_SEGMENT 5 #define DEFINED_RW_SEGMENT 6 #define LARGE_DEFINED_PC_OFFSET 7 #define GOT_OFFSET 8 #define PLT_OFFSET 9 #define GOTOFF_OFFSET 10 /* Initialize the relax table. */ const relax_typeS md_relax_table[] = { { 1, 1, 0, 0 }, /* 0: Unused. */ { 1, 1, 0, 0 }, /* 1: Unused. */ { 1, 1, 0, 0 }, /* 2: Unused. */ { 1, 1, 0, 0 }, /* 3: Unused. */ { 32767, -32768, INST_WORD_SIZE, LARGE_DEFINED_PC_OFFSET }, /* 4: DEFINED_PC_OFFSET. */ { 1, 1, 0, 0 }, /* 5: Unused. */ { 1, 1, 0, 0 }, /* 6: Unused. */ { 0x7fffffff, 0x80000000, INST_WORD_SIZE*2, 0 }, /* 7: LARGE_DEFINED_PC_OFFSET. */ { 0x7fffffff, 0x80000000, INST_WORD_SIZE*2, 0 }, /* 8: GOT_OFFSET. */ { 0x7fffffff, 0x80000000, INST_WORD_SIZE*2, 0 }, /* 9: PLT_OFFSET. */ { 0x7fffffff, 0x80000000, INST_WORD_SIZE*2, 0 }, /* 10: GOTOFF_OFFSET. */ }; static struct hash_control * opcode_hash_control; /* Opcode mnemonics. */ static segT sbss_segment = 0; /* Small bss section. */ static segT sbss2_segment = 0; /* Section not used. */ static segT sdata_segment = 0; /* Small data section. */ static segT sdata2_segment = 0; /* Small read-only section. */ static segT rodata_segment = 0; /* read-only section. */ /* Generate a symbol for stabs information. */ void microblaze_generate_symbol (char *sym) { #define MICROBLAZE_FAKE_LABEL_NAME "XL0\001" static int microblaze_label_count; sprintf (sym, "%sL%d", MICROBLAZE_FAKE_LABEL_NAME, microblaze_label_count); ++microblaze_label_count; } /* Handle the section changing pseudo-ops. */ static void microblaze_s_text (int ignore ATTRIBUTE_UNUSED) { #ifdef OBJ_ELF obj_elf_text (ignore); #else s_text (ignore); #endif } static void microblaze_s_data (int ignore ATTRIBUTE_UNUSED) { #ifdef OBJ_ELF obj_elf_change_section (".data", SHT_PROGBITS, SHF_ALLOC+SHF_WRITE, 0, 0, 0, 0); #else s_data (ignore); #endif } /* Things in the .sdata segment are always considered to be in the small data section. */ static void microblaze_s_sdata (int ignore ATTRIBUTE_UNUSED) { #ifdef OBJ_ELF obj_elf_change_section (".sdata", SHT_PROGBITS, SHF_ALLOC+SHF_WRITE, 0, 0, 0, 0); #else s_data (ignore); #endif } /* Pseudo op to make file scope bss items. */ static void microblaze_s_lcomm (int xxx ATTRIBUTE_UNUSED) { char *name; char c; char *p; offsetT size; symbolS *symbolP; offsetT align; char *pfrag; int align2; segT current_seg = now_seg; subsegT current_subseg = now_subseg; name = input_line_pointer; c = get_symbol_end (); /* Just after name is now '\0'. */ p = input_line_pointer; *p = c; SKIP_WHITESPACE (); if (*input_line_pointer != ',') { as_bad (_("Expected comma after symbol-name: rest of line ignored.")); ignore_rest_of_line (); return; } input_line_pointer++; /* skip ',' */ if ((size = get_absolute_expression ()) < 0) { as_warn (_(".COMMon length (%ld.) <0! Ignored."), (long) size); ignore_rest_of_line (); return; } /* The third argument to .lcomm is the alignment. */ if (*input_line_pointer != ',') align = 8; else { ++input_line_pointer; align = get_absolute_expression (); if (align <= 0) { as_warn (_("ignoring bad alignment")); align = 8; } } *p = 0; symbolP = symbol_find_or_make (name); *p = c; if (S_IS_DEFINED (symbolP) && ! S_IS_COMMON (symbolP)) { as_bad (_("Ignoring attempt to re-define symbol `%s'."), S_GET_NAME (symbolP)); ignore_rest_of_line (); return; } if (S_GET_VALUE (symbolP) && S_GET_VALUE (symbolP) != (valueT) size) { as_bad (_("Length of .lcomm \"%s\" is already %ld. Not changed to %ld."), S_GET_NAME (symbolP), (long) S_GET_VALUE (symbolP), (long) size); ignore_rest_of_line (); return; } /* Allocate_bss. */ if (align) { /* Convert to a power of 2 alignment. */ for (align2 = 0; (align & 1) == 0; align >>= 1, ++align2); if (align != 1) { as_bad (_("Common alignment not a power of 2")); ignore_rest_of_line (); return; } } else align2 = 0; record_alignment (current_seg, align2); subseg_set (current_seg, current_subseg); if (align2) frag_align (align2, 0, 0); if (S_GET_SEGMENT (symbolP) == current_seg) symbol_get_frag (symbolP)->fr_symbol = 0; symbol_set_frag (symbolP, frag_now); pfrag = frag_var (rs_org, 1, 1, (relax_substateT) 0, symbolP, size, (char *) 0); *pfrag = 0; S_SET_SIZE (symbolP, size); S_SET_SEGMENT (symbolP, current_seg); subseg_set (current_seg, current_subseg); demand_empty_rest_of_line (); } static void microblaze_s_rdata (int localvar) { #ifdef OBJ_ELF if (localvar == 0) { /* rodata. */ obj_elf_change_section (".rodata", SHT_PROGBITS, SHF_ALLOC, 0, 0, 0, 0); if (rodata_segment == 0) rodata_segment = subseg_new (".rodata", 0); } else { /* 1 .sdata2. */ obj_elf_change_section (".sdata2", SHT_PROGBITS, SHF_ALLOC, 0, 0, 0, 0); } #else s_data (ignore); #endif } static void microblaze_s_bss (int localvar) { #ifdef OBJ_ELF if (localvar == 0) /* bss. */ obj_elf_change_section (".bss", SHT_NOBITS, SHF_ALLOC+SHF_WRITE, 0, 0, 0, 0); else if (localvar == 1) { /* sbss. */ obj_elf_change_section (".sbss", SHT_NOBITS, SHF_ALLOC+SHF_WRITE, 0, 0, 0, 0); if (sbss_segment == 0) sbss_segment = subseg_new (".sbss", 0); } #else s_data (ignore); #endif } /* endp_p is always 1 as this func is called only for .end This func consumes the and calls regular processing s_func(1) with arg 1 (1 for end). */ static void microblaze_s_func (int end_p ATTRIBUTE_UNUSED) { *input_line_pointer = get_symbol_end (); s_func (1); } /* Handle the .weakext pseudo-op as defined in Kane and Heinrich. */ static void microblaze_s_weakext (int ignore ATTRIBUTE_UNUSED) { char *name; int c; symbolS *symbolP; expressionS exp; name = input_line_pointer; c = get_symbol_end (); symbolP = symbol_find_or_make (name); S_SET_WEAK (symbolP); *input_line_pointer = c; SKIP_WHITESPACE (); if (!is_end_of_line[(unsigned char) *input_line_pointer]) { if (S_IS_DEFINED (symbolP)) { as_bad ("Ignoring attempt to redefine symbol `%s'.", S_GET_NAME (symbolP)); ignore_rest_of_line (); return; } if (*input_line_pointer == ',') { ++input_line_pointer; SKIP_WHITESPACE (); } expression (&exp); if (exp.X_op != O_symbol) { as_bad ("bad .weakext directive"); ignore_rest_of_line (); return; } symbol_set_value_expression (symbolP, &exp); } demand_empty_rest_of_line (); } /* This table describes all the machine specific pseudo-ops the assembler has to support. The fields are: Pseudo-op name without dot Function to call to execute this pseudo-op Integer arg to pass to the function. */ /* If the pseudo-op is not found in this table, it searches in the obj-elf.c, and then in the read.c table. */ const pseudo_typeS md_pseudo_table[] = { {"lcomm", microblaze_s_lcomm, 1}, {"data", microblaze_s_data, 0}, {"data8", cons, 1}, /* Same as byte. */ {"data16", cons, 2}, /* Same as hword. */ {"data32", cons, 4}, /* Same as word. */ {"ent", s_func, 0}, /* Treat ent as function entry point. */ {"end", microblaze_s_func, 1}, /* Treat end as function end point. */ {"gpword", s_rva, 4}, /* gpword label => store resolved label address in data section. */ {"weakext", microblaze_s_weakext, 0}, {"rodata", microblaze_s_rdata, 0}, {"sdata2", microblaze_s_rdata, 1}, {"sdata", microblaze_s_sdata, 0}, {"bss", microblaze_s_bss, 0}, {"sbss", microblaze_s_bss, 1}, {"text", microblaze_s_text, 0}, {"word", cons, 4}, {"frame", s_ignore, 0}, {"mask", s_ignore, 0}, /* Emitted by gcc. */ {NULL, NULL, 0} }; /* 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) { struct op_code_struct * opcode; opcode_hash_control = hash_new (); /* Insert unique names into hash table. */ for (opcode = opcodes; opcode->name; opcode ++) hash_insert (opcode_hash_control, opcode->name, (char *) opcode); } /* Try to parse a reg name. */ static char * parse_reg (char * s, unsigned * reg) { unsigned tmpreg = 0; /* Strip leading whitespace. */ while (ISSPACE (* s)) ++ s; if (strncasecmp (s, "rpc", 3) == 0) { *reg = REG_PC; return s + 3; } else if (strncasecmp (s, "rmsr", 4) == 0) { *reg = REG_MSR; return s + 4; } else if (strncasecmp (s, "rear", 4) == 0) { *reg = REG_EAR; return s + 4; } else if (strncasecmp (s, "resr", 4) == 0) { *reg = REG_ESR; return s + 4; } else if (strncasecmp (s, "rfsr", 4) == 0) { *reg = REG_FSR; return s + 4; } else if (strncasecmp (s, "rbtr", 4) == 0) { *reg = REG_BTR; return s + 4; } else if (strncasecmp (s, "redr", 4) == 0) { *reg = REG_EDR; return s + 4; } /* MMU registers start. */ else if (strncasecmp (s, "rpid", 4) == 0) { *reg = REG_PID; return s + 4; } else if (strncasecmp (s, "rzpr", 4) == 0) { *reg = REG_ZPR; return s + 4; } else if (strncasecmp (s, "rtlbx", 5) == 0) { *reg = REG_TLBX; return s + 5; } else if (strncasecmp (s, "rtlblo", 6) == 0) { *reg = REG_TLBLO; return s + 6; } else if (strncasecmp (s, "rtlbhi", 6) == 0) { *reg = REG_TLBHI; return s + 6; } else if (strncasecmp (s, "rtlbsx", 6) == 0) { *reg = REG_TLBSX; return s + 6; } /* MMU registers end. */ else if (strncasecmp (s, "rpvr", 4) == 0) { if (ISDIGIT (s[4]) && ISDIGIT (s[5])) { tmpreg = (s[4]-'0')*10 + s[5] - '0'; s += 6; } else if (ISDIGIT (s[4])) { tmpreg = s[4] - '0'; s += 5; } else as_bad (_("register expected, but saw '%.6s'"), s); if ((int) tmpreg >= MIN_PVR_REGNUM && tmpreg <= MAX_PVR_REGNUM) *reg = REG_PVR + tmpreg; else { as_bad (_("Invalid register number at '%.6s'"), s); *reg = REG_PVR; } return s; } else if (strncasecmp (s, "rsp", 3) == 0) { *reg = REG_SP; return s + 3; } else if (strncasecmp (s, "rfsl", 4) == 0) { if (ISDIGIT (s[4]) && ISDIGIT (s[5])) { tmpreg = (s[4] - '0') * 10 + s[5] - '0'; s += 6; } else if (ISDIGIT (s[4])) { tmpreg = s[4] - '0'; s += 5; } else as_bad (_("register expected, but saw '%.6s'"), s); if ((int) tmpreg >= MIN_REGNUM && tmpreg <= MAX_REGNUM) *reg = tmpreg; else { as_bad (_("Invalid register number at '%.6s'"), s); *reg = 0; } return s; } else { if (TOLOWER (s[0]) == 'r') { if (ISDIGIT (s[1]) && ISDIGIT (s[2])) { tmpreg = (s[1] - '0') * 10 + s[2] - '0'; s += 3; } else if (ISDIGIT (s[1])) { tmpreg = s[1] - '0'; s += 2; } else as_bad (_("register expected, but saw '%.6s'"), s); if ((int)tmpreg >= MIN_REGNUM && tmpreg <= MAX_REGNUM) *reg = tmpreg; else { as_bad (_("Invalid register number at '%.6s'"), s); *reg = 0; } return s; } } as_bad (_("register expected, but saw '%.6s'"), s); *reg = 0; return s; } static char * parse_exp (char *s, expressionS *e) { char *save; char *new_pointer; /* Skip whitespace. */ while (ISSPACE (* s)) ++ s; save = input_line_pointer; input_line_pointer = s; expression (e); if (e->X_op == O_absent) as_fatal (_("missing operand")); new_pointer = input_line_pointer; input_line_pointer = save; return new_pointer; } /* Symbol modifiers (@GOT, @PLT, @GOTOFF). */ #define IMM_GOT 1 #define IMM_PLT 2 #define IMM_GOTOFF 3 static symbolS * GOT_symbol; #define GOT_SYMBOL_NAME "_GLOBAL_OFFSET_TABLE_" static char * parse_imm (char * s, expressionS * e, int min, int max) { char *new_pointer; char *atp; /* Find the start of "@GOT" or "@PLT" suffix (if any) */ for (atp = s; *atp != '@'; atp++) if (is_end_of_line[(unsigned char) *atp]) break; if (*atp == '@') { if (strncmp (atp + 1, "GOTOFF", 5) == 0) { *atp = 0; e->X_md = IMM_GOTOFF; } else if (strncmp (atp + 1, "GOT", 3) == 0) { *atp = 0; e->X_md = IMM_GOT; } else if (strncmp (atp + 1, "PLT", 3) == 0) { *atp = 0; e->X_md = IMM_PLT; } else { atp = NULL; e->X_md = 0; } *atp = 0; } else { atp = NULL; e->X_md = 0; } if (atp && !GOT_symbol) { GOT_symbol = symbol_find_or_make (GOT_SYMBOL_NAME); } new_pointer = parse_exp (s, e); if (e->X_op == O_absent) ; /* An error message has already been emitted. */ else if ((e->X_op != O_constant && e->X_op != O_symbol) ) as_fatal (_("operand must be a constant or a label")); else if ((e->X_op == O_constant) && ((int) e->X_add_number < min || (int) e->X_add_number > max)) { as_fatal (_("operand must be absolute in range %d..%d, not %d"), min, max, (int) e->X_add_number); } if (atp) { *atp = '@'; /* restore back (needed?) */ if (new_pointer >= atp) new_pointer += (e->X_md == IMM_GOTOFF)?7:4; /* sizeof("@GOTOFF", "@GOT" or "@PLT") */ } return new_pointer; } static char * check_got (int * got_type, int * got_len) { char *new_pointer; char *atp; char *past_got; int first, second; char *tmpbuf; /* Find the start of "@GOT" or "@PLT" suffix (if any). */ for (atp = input_line_pointer; *atp != '@'; atp++) if (is_end_of_line[(unsigned char) *atp]) return NULL; if (strncmp (atp + 1, "GOTOFF", 5) == 0) { *got_len = 6; *got_type = IMM_GOTOFF; } else if (strncmp (atp + 1, "GOT", 3) == 0) { *got_len = 3; *got_type = IMM_GOT; } else if (strncmp (atp + 1, "PLT", 3) == 0) { *got_len = 3; *got_type = IMM_PLT; } else return NULL; if (!GOT_symbol) GOT_symbol = symbol_find_or_make (GOT_SYMBOL_NAME); first = atp - input_line_pointer; past_got = atp + *got_len + 1; for (new_pointer = past_got; !is_end_of_line[(unsigned char) *new_pointer++];) ; second = new_pointer - past_got; tmpbuf = xmalloc (first + second + 2); /* One extra byte for ' ' and one for NUL. */ memcpy (tmpbuf, input_line_pointer, first); tmpbuf[first] = ' '; /* @GOTOFF is replaced with a single space. */ memcpy (tmpbuf + first + 1, past_got, second); tmpbuf[first + second + 1] = '\0'; return tmpbuf; } extern void parse_cons_expression_microblaze (expressionS *exp, int size) { if (size == 4) { /* Handle @GOTOFF et.al. */ char *save, *gotfree_copy; int got_len, got_type; save = input_line_pointer; gotfree_copy = check_got (& got_type, & got_len); if (gotfree_copy) input_line_pointer = gotfree_copy; expression (exp); if (gotfree_copy) { exp->X_md = got_type; input_line_pointer = save + (input_line_pointer - gotfree_copy) + got_len; free (gotfree_copy); } } else expression (exp); } /* This is the guts of the machine-dependent assembler. STR points to a machine dependent instruction. This function is supposed to emit the frags/bytes it assembles to. */ static char * str_microblaze_ro_anchor = "RO"; static char * str_microblaze_rw_anchor = "RW"; static bfd_boolean check_spl_reg (unsigned * reg) { if ((*reg == REG_MSR) || (*reg == REG_PC) || (*reg == REG_EAR) || (*reg == REG_ESR) || (*reg == REG_FSR) || (*reg == REG_BTR) || (*reg == REG_EDR) || (*reg == REG_PID) || (*reg == REG_ZPR) || (*reg == REG_TLBX) || (*reg == REG_TLBLO) || (*reg == REG_TLBHI) || (*reg == REG_TLBSX) || (*reg >= REG_PVR+MIN_PVR_REGNUM && *reg <= REG_PVR+MAX_PVR_REGNUM)) return TRUE; return FALSE; } /* Here we decide which fixups can be adjusted to make them relative to the beginning of the section instead of the symbol. Basically we need to make sure that the dynamic relocations are done correctly, so in some cases we force the original symbol to be used. */ int tc_microblaze_fix_adjustable (struct fix *fixP) { if (GOT_symbol && fixP->fx_subsy == GOT_symbol) return 0; if (fixP->fx_r_type == BFD_RELOC_MICROBLAZE_64_GOTOFF || fixP->fx_r_type == BFD_RELOC_MICROBLAZE_32_GOTOFF || fixP->fx_r_type == BFD_RELOC_MICROBLAZE_64_GOT || fixP->fx_r_type == BFD_RELOC_MICROBLAZE_64_PLT) return 0; return 1; } void md_assemble (char * str) { char * op_start; char * op_end; struct op_code_struct * opcode, *opcode1; char * output = NULL; int nlen = 0; int i; unsigned long inst, inst1; unsigned reg1; unsigned reg2; unsigned reg3; unsigned isize; unsigned int immed, temp; expressionS exp; char name[20]; /* Drop leading whitespace. */ while (ISSPACE (* str)) str ++; /* Find the op code end. */ for (op_start = op_end = str; *op_end && !is_end_of_line[(unsigned char) *op_end] && *op_end != ' '; op_end++) { name[nlen] = op_start[nlen]; nlen++; if (nlen == sizeof (name) - 1) break; } name [nlen] = 0; if (nlen == 0) { as_bad (_("can't find opcode ")); return; } opcode = (struct op_code_struct *) hash_find (opcode_hash_control, name); if (opcode == NULL) { as_bad (_("unknown opcode \"%s\""), name); return; } inst = opcode->bit_sequence; isize = 4; switch (opcode->inst_type) { case INST_TYPE_RD_R1_R2: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®2); /* Get r1. */ else { as_fatal (_("Error in statement syntax")); reg2 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®3); /* Get r2. */ else { as_fatal (_("Error in statement syntax")); reg3 = 0; } /* Check for spl registers. */ if (check_spl_reg (& reg1)) as_fatal (_("Cannot use special register with this instruction")); if (check_spl_reg (& reg2)) as_fatal (_("Cannot use special register with this instruction")); if (check_spl_reg (& reg3)) as_fatal (_("Cannot use special register with this instruction")); if (streq (name, "sub")) { /* sub rd, r1, r2 becomes rsub rd, r2, r1. */ inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (reg3 << RA_LOW) & RA_MASK; inst |= (reg2 << RB_LOW) & RB_MASK; } else { inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (reg2 << RA_LOW) & RA_MASK; inst |= (reg3 << RB_LOW) & RB_MASK; } output = frag_more (isize); break; case INST_TYPE_RD_R1_IMM: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®2); /* Get r1. */ else { as_fatal (_("Error in statement syntax")); reg2 = 0; } if (strcmp (op_end, "")) op_end = parse_imm (op_end + 1, & exp, MIN_IMM, MAX_IMM); else as_fatal (_("Error in statement syntax")); /* Check for spl registers. */ if (check_spl_reg (& reg1)) as_fatal (_("Cannot use special register with this instruction")); if (check_spl_reg (& reg2)) as_fatal (_("Cannot use special register with this instruction")); if (exp.X_op != O_constant) { char *opc; relax_substateT subtype; if (streq (name, "lmi")) as_fatal (_("lmi pseudo instruction should not use a label in imm field")); else if (streq (name, "smi")) as_fatal (_("smi pseudo instruction should not use a label in imm field")); if (reg2 == REG_ROSDP) opc = str_microblaze_ro_anchor; else if (reg2 == REG_RWSDP) opc = str_microblaze_rw_anchor; else opc = NULL; if (exp.X_md == IMM_GOT) subtype = GOT_OFFSET; else if (exp.X_md == IMM_PLT) subtype = PLT_OFFSET; else if (exp.X_md == IMM_GOTOFF) subtype = GOTOFF_OFFSET; else subtype = opcode->inst_offset_type; output = frag_var (rs_machine_dependent, isize * 2, /* maxm of 2 words. */ isize, /* minm of 1 word. */ subtype, /* PC-relative or not. */ exp.X_add_symbol, exp.X_add_number, opc); immed = 0; } else { output = frag_more (isize); immed = exp.X_add_number; } if (streq (name, "lmi") || streq (name, "smi")) { /* Load/store 32-d consecutive registers. Used on exit/entry to subroutines to save and restore registers to stack. Generate 32-d insts. */ int count; count = 32 - reg1; if (streq (name, "lmi")) opcode = (struct op_code_struct *) hash_find (opcode_hash_control, "lwi"); else opcode = (struct op_code_struct *) hash_find (opcode_hash_control, "swi"); if (opcode == NULL) { as_bad (_("unknown opcode \"%s\""), "lwi"); return; } inst = opcode->bit_sequence; inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (reg2 << RA_LOW) & RA_MASK; inst |= (immed << IMM_LOW) & IMM_MASK; for (i = 0; i < count - 1; i++) { output[0] = INST_BYTE0 (inst); output[1] = INST_BYTE1 (inst); output[2] = INST_BYTE2 (inst); output[3] = INST_BYTE3 (inst); output = frag_more (isize); immed = immed + 4; reg1++; inst = opcode->bit_sequence; inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (reg2 << RA_LOW) & RA_MASK; inst |= (immed << IMM_LOW) & IMM_MASK; } } else { temp = immed & 0xFFFF8000; if ((temp != 0) && (temp != 0xFFFF8000)) { /* Needs an immediate inst. */ opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm"); if (opcode1 == NULL) { as_bad (_("unknown opcode \"%s\""), "imm"); return; } inst1 = opcode1->bit_sequence; inst1 |= ((immed & 0xFFFF0000) >> 16) & IMM_MASK; output[0] = INST_BYTE0 (inst1); output[1] = INST_BYTE1 (inst1); output[2] = INST_BYTE2 (inst1); output[3] = INST_BYTE3 (inst1); output = frag_more (isize); } inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (reg2 << RA_LOW) & RA_MASK; inst |= (immed << IMM_LOW) & IMM_MASK; } break; case INST_TYPE_RD_R1_IMM5: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®2); /* Get r1. */ else { as_fatal (_("Error in statement syntax")); reg2 = 0; } if (strcmp (op_end, "")) op_end = parse_imm (op_end + 1, & exp, MIN_IMM, MAX_IMM); else as_fatal (_("Error in statement syntax")); /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); if (check_spl_reg (®2)) as_fatal (_("Cannot use special register with this instruction")); if (exp.X_op != O_constant) as_warn (_("Symbol used as immediate for shift instruction")); else { output = frag_more (isize); immed = exp.X_add_number; } if (immed != (immed % 32)) { as_warn (_("Shift value > 32. using ")); immed = immed % 32; } inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (reg2 << RA_LOW) & RA_MASK; inst |= (immed << IMM_LOW) & IMM5_MASK; break; case INST_TYPE_R1_R2: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get r1. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®2); /* Get r2. */ else { as_fatal (_("Error in statement syntax")); reg2 = 0; } /* Check for spl registers. */ if (check_spl_reg (& reg1)) as_fatal (_("Cannot use special register with this instruction")); if (check_spl_reg (& reg2)) as_fatal (_("Cannot use special register with this instruction")); inst |= (reg1 << RA_LOW) & RA_MASK; inst |= (reg2 << RB_LOW) & RB_MASK; output = frag_more (isize); break; case INST_TYPE_RD_R1: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®2); /* Get r1. */ else { as_fatal (_("Error in statement syntax")); reg2 =0; } /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); if (check_spl_reg (®2)) as_fatal (_("Cannot use special register with this instruction")); inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (reg2 << RA_LOW) & RA_MASK; output = frag_more (isize); break; case INST_TYPE_RD_RFSL: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, &immed); /* Get rfslN. */ else { as_fatal (_("Error in statement syntax")); immed = 0; } /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (immed << IMM_LOW) & RFSL_MASK; output = frag_more (isize); break; case INST_TYPE_RD_IMM15: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_imm (op_end + 1, & exp, MIN_IMM15, MAX_IMM15); else as_fatal (_("Error in statement syntax")); /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); if (exp.X_op != O_constant) as_fatal (_("Symbol used as immediate value for msrset/msrclr instructions")); else { output = frag_more (isize); immed = exp.X_add_number; } inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (immed << IMM_LOW) & IMM15_MASK; break; case INST_TYPE_R1_RFSL: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get r1. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, &immed); /* Get rfslN. */ else { as_fatal (_("Error in statement syntax")); immed = 0; } /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); inst |= (reg1 << RA_LOW) & RA_MASK; inst |= (immed << IMM_LOW) & RFSL_MASK; output = frag_more (isize); break; case INST_TYPE_RFSL: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, &immed); /* Get rfslN. */ else { as_fatal (_("Error in statement syntax")); immed = 0; } /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); inst |= (immed << IMM_LOW) & RFSL_MASK; output = frag_more (isize); break; case INST_TYPE_R1: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get r1. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); inst |= (reg1 << RA_LOW) & RA_MASK; output = frag_more (isize); break; /* For tuqula insn...:) */ case INST_TYPE_RD: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); inst |= (reg1 << RD_LOW) & RD_MASK; output = frag_more (isize); break; case INST_TYPE_RD_SPECIAL: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®2); /* Get r1. */ else { as_fatal (_("Error in statement syntax")); reg2 = 0; } if (reg2 == REG_MSR) immed = opcode->immval_mask | REG_MSR_MASK; else if (reg2 == REG_PC) immed = opcode->immval_mask | REG_PC_MASK; else if (reg2 == REG_EAR) immed = opcode->immval_mask | REG_EAR_MASK; else if (reg2 == REG_ESR) immed = opcode->immval_mask | REG_ESR_MASK; else if (reg2 == REG_FSR) immed = opcode->immval_mask | REG_FSR_MASK; else if (reg2 == REG_BTR) immed = opcode->immval_mask | REG_BTR_MASK; else if (reg2 == REG_EDR) immed = opcode->immval_mask | REG_EDR_MASK; else if (reg2 == REG_PID) immed = opcode->immval_mask | REG_PID_MASK; else if (reg2 == REG_ZPR) immed = opcode->immval_mask | REG_ZPR_MASK; else if (reg2 == REG_TLBX) immed = opcode->immval_mask | REG_TLBX_MASK; else if (reg2 == REG_TLBLO) immed = opcode->immval_mask | REG_TLBLO_MASK; else if (reg2 == REG_TLBHI) immed = opcode->immval_mask | REG_TLBHI_MASK; else if (reg2 >= (REG_PVR+MIN_PVR_REGNUM) && reg2 <= (REG_PVR+MAX_PVR_REGNUM)) immed = opcode->immval_mask | REG_PVR_MASK | reg2; else as_fatal (_("invalid value for special purpose register")); inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (immed << IMM_LOW) & IMM_MASK; output = frag_more (isize); break; case INST_TYPE_SPECIAL_R1: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®2); /* Get r1. */ else { as_fatal (_("Error in statement syntax")); reg2 = 0; } if (reg1 == REG_MSR) immed = opcode->immval_mask | REG_MSR_MASK; else if (reg1 == REG_PC) immed = opcode->immval_mask | REG_PC_MASK; else if (reg1 == REG_EAR) immed = opcode->immval_mask | REG_EAR_MASK; else if (reg1 == REG_ESR) immed = opcode->immval_mask | REG_ESR_MASK; else if (reg1 == REG_FSR) immed = opcode->immval_mask | REG_FSR_MASK; else if (reg1 == REG_BTR) immed = opcode->immval_mask | REG_BTR_MASK; else if (reg1 == REG_EDR) immed = opcode->immval_mask | REG_EDR_MASK; else if (reg1 == REG_PID) immed = opcode->immval_mask | REG_PID_MASK; else if (reg1 == REG_ZPR) immed = opcode->immval_mask | REG_ZPR_MASK; else if (reg1 == REG_TLBX) immed = opcode->immval_mask | REG_TLBX_MASK; else if (reg1 == REG_TLBLO) immed = opcode->immval_mask | REG_TLBLO_MASK; else if (reg1 == REG_TLBHI) immed = opcode->immval_mask | REG_TLBHI_MASK; else if (reg1 == REG_TLBSX) immed = opcode->immval_mask | REG_TLBSX_MASK; else as_fatal (_("invalid value for special purpose register")); inst |= (reg2 << RA_LOW) & RA_MASK; inst |= (immed << IMM_LOW) & IMM_MASK; output = frag_more (isize); break; case INST_TYPE_RD_R1_SPECIAL: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®2); /* Get r1. */ else { as_fatal (_("Error in statement syntax")); reg2 =0; } /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); if (check_spl_reg (®2)) as_fatal (_("Cannot use special register with this instruction")); /* insn wic ra, rb => wic ra, ra, rb. */ inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (reg1 << RA_LOW) & RA_MASK; inst |= (reg2 << RB_LOW) & RB_MASK; output = frag_more (isize); break; case INST_TYPE_RD_R2: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®2); /* Get r2. */ else { as_fatal (_("Error in statement syntax")); reg2 = 0; } /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); if (check_spl_reg (®2)) as_fatal (_("Cannot use special register with this instruction")); inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (reg2 << RB_LOW) & RB_MASK; output = frag_more (isize); break; case INST_TYPE_R1_IMM: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get r1. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_imm (op_end + 1, & exp, MIN_IMM, MAX_IMM); else as_fatal (_("Error in statement syntax")); /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); if (exp.X_op != O_constant) { char *opc = NULL; relax_substateT subtype; if (exp.X_md == IMM_GOT) subtype = GOT_OFFSET; else if (exp.X_md == IMM_PLT) subtype = PLT_OFFSET; else subtype = opcode->inst_offset_type; output = frag_var (rs_machine_dependent, isize * 2, /* maxm of 2 words. */ isize, /* minm of 1 word. */ subtype, /* PC-relative or not. */ exp.X_add_symbol, exp.X_add_number, opc); immed = 0; } else { output = frag_more (isize); immed = exp.X_add_number; } temp = immed & 0xFFFF8000; if ((temp != 0) && (temp != 0xFFFF8000)) { /* Needs an immediate inst. */ opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm"); if (opcode1 == NULL) { as_bad (_("unknown opcode \"%s\""), "imm"); return; } inst1 = opcode1->bit_sequence; inst1 |= ((immed & 0xFFFF0000) >> 16) & IMM_MASK; output[0] = INST_BYTE0 (inst1); output[1] = INST_BYTE1 (inst1); output[2] = INST_BYTE2 (inst1); output[3] = INST_BYTE3 (inst1); output = frag_more (isize); } inst |= (reg1 << RA_LOW) & RA_MASK; inst |= (immed << IMM_LOW) & IMM_MASK; break; case INST_TYPE_RD_IMM: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®1); /* Get rd. */ else { as_fatal (_("Error in statement syntax")); reg1 = 0; } if (strcmp (op_end, "")) op_end = parse_imm (op_end + 1, & exp, MIN_IMM, MAX_IMM); else as_fatal (_("Error in statement syntax")); /* Check for spl registers. */ if (check_spl_reg (®1)) as_fatal (_("Cannot use special register with this instruction")); if (exp.X_op != O_constant) { char *opc = NULL; relax_substateT subtype; if (exp.X_md == IMM_GOT) subtype = GOT_OFFSET; else if (exp.X_md == IMM_PLT) subtype = PLT_OFFSET; else subtype = opcode->inst_offset_type; output = frag_var (rs_machine_dependent, isize * 2, /* maxm of 2 words. */ isize, /* minm of 1 word. */ subtype, /* PC-relative or not. */ exp.X_add_symbol, exp.X_add_number, opc); immed = 0; } else { output = frag_more (isize); immed = exp.X_add_number; } temp = immed & 0xFFFF8000; if ((temp != 0) && (temp != 0xFFFF8000)) { /* Needs an immediate inst. */ opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm"); if (opcode1 == NULL) { as_bad (_("unknown opcode \"%s\""), "imm"); return; } inst1 = opcode1->bit_sequence; inst1 |= ((immed & 0xFFFF0000) >> 16) & IMM_MASK; output[0] = INST_BYTE0 (inst1); output[1] = INST_BYTE1 (inst1); output[2] = INST_BYTE2 (inst1); output[3] = INST_BYTE3 (inst1); output = frag_more (isize); } inst |= (reg1 << RD_LOW) & RD_MASK; inst |= (immed << IMM_LOW) & IMM_MASK; break; case INST_TYPE_R2: if (strcmp (op_end, "")) op_end = parse_reg (op_end + 1, ®2); /* Get r2. */ else { as_fatal (_("Error in statement syntax")); reg2 = 0; } /* Check for spl registers. */ if (check_spl_reg (®2)) as_fatal (_("Cannot use special register with this instruction")); inst |= (reg2 << RB_LOW) & RB_MASK; output = frag_more (isize); break; case INST_TYPE_IMM: if (streq (name, "imm")) as_fatal (_("An IMM instruction should not be present in the .s file")); op_end = parse_imm (op_end + 1, & exp, MIN_IMM, MAX_IMM); if (exp.X_op != O_constant) { char *opc = NULL; relax_substateT subtype; if (exp.X_md == IMM_GOT) subtype = GOT_OFFSET; else if (exp.X_md == IMM_PLT) subtype = PLT_OFFSET; else subtype = opcode->inst_offset_type; output = frag_var (rs_machine_dependent, isize * 2, /* maxm of 2 words. */ isize, /* minm of 1 word. */ subtype, /* PC-relative or not. */ exp.X_add_symbol, exp.X_add_number, opc); immed = 0; } else { output = frag_more (isize); immed = exp.X_add_number; } temp = immed & 0xFFFF8000; if ((temp != 0) && (temp != 0xFFFF8000)) { /* Needs an immediate inst. */ opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm"); if (opcode1 == NULL) { as_bad (_("unknown opcode \"%s\""), "imm"); return; } inst1 = opcode1->bit_sequence; inst1 |= ((immed & 0xFFFF0000) >> 16) & IMM_MASK; output[0] = INST_BYTE0 (inst1); output[1] = INST_BYTE1 (inst1); output[2] = INST_BYTE2 (inst1); output[3] = INST_BYTE3 (inst1); output = frag_more (isize); } inst |= (immed << IMM_LOW) & IMM_MASK; break; case INST_TYPE_NONE: output = frag_more (isize); break; default: as_fatal (_("unimplemented opcode \"%s\""), name); } /* Drop whitespace after all the operands have been parsed. */ while (ISSPACE (* op_end)) op_end ++; /* Give warning message if the insn has more operands than required. */ if (strcmp (op_end, opcode->name) && strcmp (op_end, "")) as_warn (_("ignoring operands: %s "), op_end); output[0] = INST_BYTE0 (inst); output[1] = INST_BYTE1 (inst); output[2] = INST_BYTE2 (inst); output[3] = INST_BYTE3 (inst); #ifdef OBJ_ELF dwarf2_emit_insn (4); #endif } symbolS * md_undefined_symbol (char * name ATTRIBUTE_UNUSED) { return NULL; } /* Various routines to kill one day. */ /* Equal to MAX_PRECISION in atof-ieee.c */ #define MAX_LITTLENUMS 6 /* 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.*/ char * md_atof (int type, char * litP, int * sizeP) { int prec; LITTLENUM_TYPE words[MAX_LITTLENUMS]; int i; char * t; switch (type) { case 'f': case 'F': case 's': case 'S': prec = 2; break; case 'd': case 'D': case 'r': case 'R': prec = 4; break; case 'x': case 'X': prec = 6; break; case 'p': case 'P': prec = 6; break; default: *sizeP = 0; return _("Bad call to MD_NTOF()"); } t = atof_ieee (input_line_pointer, type, words); if (t) input_line_pointer = t; *sizeP = prec * sizeof (LITTLENUM_TYPE); if (! target_big_endian) { for (i = prec - 1; i >= 0; i--) { md_number_to_chars (litP, (valueT) words[i], sizeof (LITTLENUM_TYPE)); litP += sizeof (LITTLENUM_TYPE); } } else for (i = 0; i < prec; i++) { md_number_to_chars (litP, (valueT) words[i], sizeof (LITTLENUM_TYPE)); litP += sizeof (LITTLENUM_TYPE); } return NULL; } const char * md_shortopts = ""; struct option md_longopts[] = { { NULL, no_argument, NULL, 0} }; size_t md_longopts_size = sizeof (md_longopts); int md_short_jump_size; void md_create_short_jump (char * ptr ATTRIBUTE_UNUSED, addressT from_Nddr ATTRIBUTE_UNUSED, addressT to_Nddr ATTRIBUTE_UNUSED, fragS * frag ATTRIBUTE_UNUSED, symbolS * to_symbol ATTRIBUTE_UNUSED) { as_fatal (_("failed sanity check: short_jump")); } void md_create_long_jump (char * ptr ATTRIBUTE_UNUSED, addressT from_Nddr ATTRIBUTE_UNUSED, addressT to_Nddr ATTRIBUTE_UNUSED, fragS * frag ATTRIBUTE_UNUSED, symbolS * to_symbol ATTRIBUTE_UNUSED) { as_fatal (_("failed sanity check: long_jump")); } /* Called after relaxing, change the frags so they know how big they are. */ void md_convert_frag (bfd * abfd ATTRIBUTE_UNUSED, segT sec ATTRIBUTE_UNUSED, fragS * fragP) { fixS *fixP; switch (fragP->fr_subtype) { case UNDEFINED_PC_OFFSET: fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol, fragP->fr_offset, TRUE, BFD_RELOC_64_PCREL); fragP->fr_fix += INST_WORD_SIZE * 2; fragP->fr_var = 0; break; case DEFINED_ABS_SEGMENT: if (fragP->fr_symbol == GOT_symbol) fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol, fragP->fr_offset, TRUE, BFD_RELOC_MICROBLAZE_64_GOTPC); else fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol, fragP->fr_offset, FALSE, BFD_RELOC_64); fragP->fr_fix += INST_WORD_SIZE * 2; fragP->fr_var = 0; break; case DEFINED_RO_SEGMENT: fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE, fragP->fr_symbol, fragP->fr_offset, FALSE, BFD_RELOC_MICROBLAZE_32_ROSDA); fragP->fr_fix += INST_WORD_SIZE; fragP->fr_var = 0; break; case DEFINED_RW_SEGMENT: fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE, fragP->fr_symbol, fragP->fr_offset, FALSE, BFD_RELOC_MICROBLAZE_32_RWSDA); fragP->fr_fix += INST_WORD_SIZE; fragP->fr_var = 0; break; case DEFINED_PC_OFFSET: fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE, fragP->fr_symbol, fragP->fr_offset, TRUE, BFD_RELOC_MICROBLAZE_32_LO_PCREL); fragP->fr_fix += INST_WORD_SIZE; fragP->fr_var = 0; break; case LARGE_DEFINED_PC_OFFSET: fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol, fragP->fr_offset, TRUE, BFD_RELOC_64_PCREL); fragP->fr_fix += INST_WORD_SIZE * 2; fragP->fr_var = 0; break; case GOT_OFFSET: fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol, fragP->fr_offset, FALSE, BFD_RELOC_MICROBLAZE_64_GOT); fragP->fr_fix += INST_WORD_SIZE * 2; fragP->fr_var = 0; break; case PLT_OFFSET: fixP = fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol, fragP->fr_offset, TRUE, BFD_RELOC_MICROBLAZE_64_PLT); /* fixP->fx_plt = 1; */ (void) fixP; fragP->fr_fix += INST_WORD_SIZE * 2; fragP->fr_var = 0; break; case GOTOFF_OFFSET: fix_new (fragP, fragP->fr_fix, INST_WORD_SIZE * 2, fragP->fr_symbol, fragP->fr_offset, FALSE, BFD_RELOC_MICROBLAZE_64_GOTOFF); fragP->fr_fix += INST_WORD_SIZE * 2; fragP->fr_var = 0; break; default: abort (); } } /* Applies the desired value to the specified location. Also sets up addends for 'rela' type relocations. */ void md_apply_fix (fixS * fixP, valueT * valp, segT segment) { char * buf = fixP->fx_where + fixP->fx_frag->fr_literal; char * file = fixP->fx_file ? fixP->fx_file : _("unknown"); const char * symname; /* Note: use offsetT because it is signed, valueT is unsigned. */ offsetT val = (offsetT) * valp; int i; struct op_code_struct * opcode1; unsigned long inst1; symname = fixP->fx_addsy ? S_GET_NAME (fixP->fx_addsy) : _(""); /* fixP->fx_offset is supposed to be set up correctly for all symbol relocations. */ if (fixP->fx_addsy == NULL) { if (!fixP->fx_pcrel) fixP->fx_offset = val; /* Absolute relocation. */ else fprintf (stderr, "NULL symbol PC-relative relocation? offset = %08x, val = %08x\n", (unsigned int) fixP->fx_offset, (unsigned int) val); } /* If we aren't adjusting this fixup to be against the section symbol, we need to adjust the value. */ if (fixP->fx_addsy != NULL) { if (S_IS_WEAK (fixP->fx_addsy) || (symbol_used_in_reloc_p (fixP->fx_addsy) && (((bfd_get_section_flags (stdoutput, S_GET_SEGMENT (fixP->fx_addsy)) & SEC_LINK_ONCE) != 0) || !strncmp (segment_name (S_GET_SEGMENT (fixP->fx_addsy)), ".gnu.linkonce", sizeof (".gnu.linkonce") - 1)))) { val -= S_GET_VALUE (fixP->fx_addsy); if (val != 0 && ! fixP->fx_pcrel) { /* In this case, the bfd_install_relocation routine will incorrectly add the symbol value back in. We just want the addend to appear in the object file. FIXME: If this makes VALUE zero, we're toast. */ val -= S_GET_VALUE (fixP->fx_addsy); } } } /* If the fix is relative to a symbol which is not defined, or not in the same segment as the fix, we cannot resolve it here. */ /* fixP->fx_addsy is NULL if valp contains the entire relocation. */ if (fixP->fx_addsy != NULL && (!S_IS_DEFINED (fixP->fx_addsy) || (S_GET_SEGMENT (fixP->fx_addsy) != segment))) { fixP->fx_done = 0; #ifdef OBJ_ELF /* For ELF we can just return and let the reloc that will be generated take care of everything. For COFF we still have to insert 'val' into the insn since the addend field will be ignored. */ /* return; */ #endif } /* All fixups in the text section must be handled in the linker. */ else if (segment->flags & SEC_CODE) fixP->fx_done = 0; else if (!fixP->fx_pcrel && fixP->fx_addsy != NULL) fixP->fx_done = 0; else fixP->fx_done = 1; switch (fixP->fx_r_type) { case BFD_RELOC_MICROBLAZE_32_LO: case BFD_RELOC_MICROBLAZE_32_LO_PCREL: if (target_big_endian) { buf[2] |= ((val >> 8) & 0xff); buf[3] |= (val & 0xff); } else { buf[1] |= ((val >> 8) & 0xff); buf[0] |= (val & 0xff); } break; case BFD_RELOC_MICROBLAZE_32_ROSDA: case BFD_RELOC_MICROBLAZE_32_RWSDA: /* Don't do anything if the symbol is not defined. */ if (fixP->fx_addsy == NULL || S_IS_DEFINED (fixP->fx_addsy)) { if (((val & 0xFFFF8000) != 0) && ((val & 0xFFFF8000) != 0xFFFF8000)) as_bad_where (file, fixP->fx_line, _("pcrel for branch to %s too far (0x%x)"), symname, (int) val); if (target_big_endian) { buf[2] |= ((val >> 8) & 0xff); buf[3] |= (val & 0xff); } else { buf[1] |= ((val >> 8) & 0xff); buf[0] |= (val & 0xff); } } break; case BFD_RELOC_32: case BFD_RELOC_RVA: case BFD_RELOC_32_PCREL: case BFD_RELOC_MICROBLAZE_32_SYM_OP_SYM: /* Don't do anything if the symbol is not defined. */ if (fixP->fx_addsy == NULL || S_IS_DEFINED (fixP->fx_addsy)) { if (target_big_endian) { buf[0] |= ((val >> 24) & 0xff); buf[1] |= ((val >> 16) & 0xff); buf[2] |= ((val >> 8) & 0xff); buf[3] |= (val & 0xff); } else { buf[3] |= ((val >> 24) & 0xff); buf[2] |= ((val >> 16) & 0xff); buf[1] |= ((val >> 8) & 0xff); buf[0] |= (val & 0xff); } } break; case BFD_RELOC_64_PCREL: case BFD_RELOC_64: /* Add an imm instruction. First save the current instruction. */ for (i = 0; i < INST_WORD_SIZE; i++) buf[i + INST_WORD_SIZE] = buf[i]; /* Generate the imm instruction. */ opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm"); if (opcode1 == NULL) { as_bad (_("unknown opcode \"%s\""), "imm"); return; } inst1 = opcode1->bit_sequence; if (fixP->fx_addsy == NULL || S_IS_DEFINED (fixP->fx_addsy)) inst1 |= ((val & 0xFFFF0000) >> 16) & IMM_MASK; buf[0] = INST_BYTE0 (inst1); buf[1] = INST_BYTE1 (inst1); buf[2] = INST_BYTE2 (inst1); buf[3] = INST_BYTE3 (inst1); /* Add the value only if the symbol is defined. */ if (fixP->fx_addsy == NULL || S_IS_DEFINED (fixP->fx_addsy)) { if (target_big_endian) { buf[6] |= ((val >> 8) & 0xff); buf[7] |= (val & 0xff); } else { buf[5] |= ((val >> 8) & 0xff); buf[4] |= (val & 0xff); } } break; case BFD_RELOC_MICROBLAZE_64_GOTPC: case BFD_RELOC_MICROBLAZE_64_GOT: case BFD_RELOC_MICROBLAZE_64_PLT: case BFD_RELOC_MICROBLAZE_64_GOTOFF: /* Add an imm instruction. First save the current instruction. */ for (i = 0; i < INST_WORD_SIZE; i++) buf[i + INST_WORD_SIZE] = buf[i]; /* Generate the imm instruction. */ opcode1 = (struct op_code_struct *) hash_find (opcode_hash_control, "imm"); if (opcode1 == NULL) { as_bad (_("unknown opcode \"%s\""), "imm"); return; } inst1 = opcode1->bit_sequence; /* We can fixup call to a defined non-global address within the same section only. */ buf[0] = INST_BYTE0 (inst1); buf[1] = INST_BYTE1 (inst1); buf[2] = INST_BYTE2 (inst1); buf[3] = INST_BYTE3 (inst1); return; default: break; } if (fixP->fx_addsy == NULL) { /* This fixup has been resolved. Create a reloc in case the linker moves code around due to relaxing. */ if (fixP->fx_r_type == BFD_RELOC_64_PCREL) fixP->fx_r_type = BFD_RELOC_MICROBLAZE_64_NONE; else fixP->fx_r_type = BFD_RELOC_NONE; fixP->fx_addsy = section_symbol (absolute_section); } return; } void md_operand (expressionS * expressionP) { /* Ignore leading hash symbol, if present. */ if (*input_line_pointer == '#') { input_line_pointer ++; expression (expressionP); } } /* Called just before address relaxation, return the length by which a fragment must grow to reach it's destination. */ int md_estimate_size_before_relax (fragS * fragP, segT segment_type) { sbss_segment = bfd_get_section_by_name (stdoutput, ".sbss"); sbss2_segment = bfd_get_section_by_name (stdoutput, ".sbss2"); sdata_segment = bfd_get_section_by_name (stdoutput, ".sdata"); sdata2_segment = bfd_get_section_by_name (stdoutput, ".sdata2"); switch (fragP->fr_subtype) { case INST_PC_OFFSET: /* Used to be a PC-relative branch. */ if (!fragP->fr_symbol) { /* We know the abs value: Should never happen. */ as_bad (_("Absolute PC-relative value in relaxation code. Assembler error.....")); abort (); } else if ((S_GET_SEGMENT (fragP->fr_symbol) == segment_type)) { fragP->fr_subtype = DEFINED_PC_OFFSET; /* Don't know now whether we need an imm instruction. */ fragP->fr_var = INST_WORD_SIZE; } else if (S_IS_DEFINED (fragP->fr_symbol) && (((S_GET_SEGMENT (fragP->fr_symbol))->flags & SEC_CODE) == 0)) { /* Cannot have a PC-relative branch to a diff segment. */ as_bad (_("PC relative branch to label %s which is not in the instruction space"), S_GET_NAME (fragP->fr_symbol)); fragP->fr_subtype = UNDEFINED_PC_OFFSET; fragP->fr_var = INST_WORD_SIZE*2; } else { fragP->fr_subtype = UNDEFINED_PC_OFFSET; fragP->fr_var = INST_WORD_SIZE*2; } break; case INST_NO_OFFSET: /* Used to be a reference to somewhere which was unknown. */ if (fragP->fr_symbol) { if (fragP->fr_opcode == NULL) { /* Used as an absolute value. */ fragP->fr_subtype = DEFINED_ABS_SEGMENT; /* Variable part does not change. */ fragP->fr_var = INST_WORD_SIZE*2; } else if (streq (fragP->fr_opcode, str_microblaze_ro_anchor)) { /* It is accessed using the small data read only anchor. */ if ((S_GET_SEGMENT (fragP->fr_symbol) == bfd_com_section_ptr) || (S_GET_SEGMENT (fragP->fr_symbol) == sdata2_segment) || (S_GET_SEGMENT (fragP->fr_symbol) == sbss2_segment) || (! S_IS_DEFINED (fragP->fr_symbol))) { fragP->fr_subtype = DEFINED_RO_SEGMENT; fragP->fr_var = INST_WORD_SIZE; } else { /* Variable not in small data read only segment accessed using small data read only anchor. */ char *file = fragP->fr_file ? fragP->fr_file : _("unknown"); as_bad_where (file, fragP->fr_line, _("Variable is accessed using small data read " "only anchor, but it is not in the small data " "read only section")); fragP->fr_subtype = DEFINED_RO_SEGMENT; fragP->fr_var = INST_WORD_SIZE; } } else if (streq (fragP->fr_opcode, str_microblaze_rw_anchor)) { if ((S_GET_SEGMENT (fragP->fr_symbol) == bfd_com_section_ptr) || (S_GET_SEGMENT (fragP->fr_symbol) == sdata_segment) || (S_GET_SEGMENT (fragP->fr_symbol) == sbss_segment) || (!S_IS_DEFINED (fragP->fr_symbol))) { /* It is accessed using the small data read write anchor. */ fragP->fr_subtype = DEFINED_RW_SEGMENT; fragP->fr_var = INST_WORD_SIZE; } else { char *file = fragP->fr_file ? fragP->fr_file : _("unknown"); as_bad_where (file, fragP->fr_line, _("Variable is accessed using small data read " "write anchor, but it is not in the small data " "read write section")); fragP->fr_subtype = DEFINED_RW_SEGMENT; fragP->fr_var = INST_WORD_SIZE; } } else { as_bad (_("Incorrect fr_opcode value in frag. Internal error.....")); abort (); } } else { /* We know the abs value: Should never happen. */ as_bad (_("Absolute value in relaxation code. Assembler error.....")); abort (); } break; case UNDEFINED_PC_OFFSET: case LARGE_DEFINED_PC_OFFSET: case DEFINED_ABS_SEGMENT: case GOT_OFFSET: case PLT_OFFSET: case GOTOFF_OFFSET: fragP->fr_var = INST_WORD_SIZE*2; break; case DEFINED_RO_SEGMENT: case DEFINED_RW_SEGMENT: case DEFINED_PC_OFFSET: fragP->fr_var = INST_WORD_SIZE; break; default: abort (); } return fragP->fr_var; } /* Put number into target byte order. */ void md_number_to_chars (char * ptr, valueT use, int nbytes) { if (target_big_endian) number_to_chars_bigendian (ptr, use, nbytes); else number_to_chars_littleendian (ptr, use, nbytes); } /* Round up a section size to the appropriate boundary. */ valueT md_section_align (segT segment ATTRIBUTE_UNUSED, valueT size) { return size; /* Byte alignment is fine. */ } /* 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 ATTRIBUTE_UNUSED) { #ifdef OBJ_ELF /* If the symbol is undefined or defined in another section we leave the add number alone for the linker to fix it later. Only account for the PC pre-bump (No PC-pre-bump on the Microblaze). */ if (fixp->fx_addsy != (symbolS *) NULL && (!S_IS_DEFINED (fixp->fx_addsy) || (S_GET_SEGMENT (fixp->fx_addsy) != sec))) return 0; else { /* The case where we are going to resolve things... */ if (fixp->fx_r_type == BFD_RELOC_64_PCREL) return fixp->fx_where + fixp->fx_frag->fr_address + INST_WORD_SIZE; else return fixp->fx_where + fixp->fx_frag->fr_address; } #endif } #define F(SZ,PCREL) (((SZ) << 1) + (PCREL)) #define MAP(SZ,PCREL,TYPE) case F (SZ, PCREL): code = (TYPE); break arelent * tc_gen_reloc (asection * section ATTRIBUTE_UNUSED, fixS * fixp) { arelent * rel; bfd_reloc_code_real_type code; switch (fixp->fx_r_type) { case BFD_RELOC_NONE: case BFD_RELOC_MICROBLAZE_64_NONE: case BFD_RELOC_32: case BFD_RELOC_MICROBLAZE_32_LO: case BFD_RELOC_MICROBLAZE_32_LO_PCREL: case BFD_RELOC_RVA: case BFD_RELOC_64: case BFD_RELOC_64_PCREL: case BFD_RELOC_MICROBLAZE_32_ROSDA: case BFD_RELOC_MICROBLAZE_32_RWSDA: case BFD_RELOC_MICROBLAZE_32_SYM_OP_SYM: case BFD_RELOC_MICROBLAZE_64_GOTPC: case BFD_RELOC_MICROBLAZE_64_GOT: case BFD_RELOC_MICROBLAZE_64_PLT: case BFD_RELOC_MICROBLAZE_64_GOTOFF: case BFD_RELOC_MICROBLAZE_32_GOTOFF: code = fixp->fx_r_type; break; default: switch (F (fixp->fx_size, fixp->fx_pcrel)) { MAP (1, 0, BFD_RELOC_8); MAP (2, 0, BFD_RELOC_16); MAP (4, 0, BFD_RELOC_32); MAP (1, 1, BFD_RELOC_8_PCREL); MAP (2, 1, BFD_RELOC_16_PCREL); MAP (4, 1, BFD_RELOC_32_PCREL); default: code = fixp->fx_r_type; as_bad (_("Can not do %d byte %srelocation"), fixp->fx_size, fixp->fx_pcrel ? _("pc-relative") : ""); } break; } rel = (arelent *) xmalloc (sizeof (arelent)); rel->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *)); if (code == BFD_RELOC_MICROBLAZE_32_SYM_OP_SYM) *rel->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_subsy); else *rel->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy); rel->address = fixp->fx_frag->fr_address + fixp->fx_where; /* Always pass the addend along! */ rel->addend = fixp->fx_offset; rel->howto = bfd_reloc_type_lookup (stdoutput, code); if (rel->howto == NULL) { as_bad_where (fixp->fx_file, fixp->fx_line, _("Cannot represent relocation type %s"), bfd_get_reloc_code_name (code)); /* Set howto to a garbage value so that we can keep going. */ rel->howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_32); gas_assert (rel->howto != NULL); } return rel; } int md_parse_option (int c, char * arg ATTRIBUTE_UNUSED) { switch (c) { default: return 0; } return 1; } void md_show_usage (FILE * stream ATTRIBUTE_UNUSED) { /* fprintf(stream, _("\ MicroBlaze options:\n\ -noSmall Data in the comm and data sections do not go into the small data section\n")); */ } /* Create a fixup for a cons expression. If parse_cons_expression_microblaze found a machine specific op in an expression, then we create relocs accordingly. */ void cons_fix_new_microblaze (fragS * frag, int where, int size, expressionS *exp) { bfd_reloc_code_real_type r; if ((exp->X_op == O_subtract) && (exp->X_add_symbol) && (exp->X_op_symbol) && (now_seg != absolute_section) && (size == 4) && (!S_IS_LOCAL (exp->X_op_symbol))) r = BFD_RELOC_MICROBLAZE_32_SYM_OP_SYM; else if (exp->X_md == IMM_GOTOFF && exp->X_op == O_symbol_rva) { exp->X_op = O_symbol; r = BFD_RELOC_MICROBLAZE_32_GOTOFF; } else { switch (size) { case 1: r = BFD_RELOC_8; break; case 2: r = BFD_RELOC_16; break; case 4: r = BFD_RELOC_32; break; case 8: r = BFD_RELOC_64; break; default: as_bad (_("unsupported BFD relocation size %u"), size); r = BFD_RELOC_32; break; } } fix_new_exp (frag, where, size, exp, 0, r); }