| /* Read coff symbol tables and convert to internal format, for GDB. |
| Copyright (C) 1987-2026 Free Software Foundation, Inc. |
| Contributed by David D. Johnson, Brown University (ddj@cs.brown.edu). |
| |
| This file is part of GDB. |
| |
| This program 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 of the License, or |
| (at your option) any later version. |
| |
| This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| #include "event-top.h" |
| #include "symtab.h" |
| #include "gdbtypes.h" |
| #include "bfd.h" |
| #include "coff/internal.h" |
| #include "libcoff.h" |
| #include "objfiles.h" |
| #include "target.h" |
| #include "dwarf2/public.h" |
| #include "coff-pe-read.h" |
| |
| /* Simplified internal version of coff symbol table information. */ |
| |
| struct coff_symbol |
| { |
| const char *c_name; |
| /* 0 if syment only, 1 if syment + auxent, etc. */ |
| int c_naux; |
| CORE_ADDR c_value; |
| int c_sclass; |
| int c_secnum; |
| unsigned int c_type; |
| }; |
| |
| /* A class that reads symbols from a COFF file. */ |
| struct coff_reader |
| { |
| explicit coff_reader (objfile *objfile) |
| : coffread_objfile (objfile), |
| symfile_bfd (objfile->obfd.get ()) |
| { |
| } |
| |
| /* Do the work of reading. */ |
| void symfile_read (symfile_add_flags symfile_flags); |
| |
| private: |
| |
| /* The objfile we are currently reading. */ |
| objfile *coffread_objfile; |
| |
| /* The BFD for this file. */ |
| bfd *symfile_bfd; |
| |
| /* Pointers to scratch storage, used for reading raw symbols and |
| auxents. */ |
| char *temp_sym; |
| char *temp_aux; |
| |
| /* Local variables that hold the shift and mask values for the |
| COFF file that we are currently reading. These come back to us |
| from BFD, and are referenced by their macro names, as well as |
| internally to the ISFCN macro from include/coff/internal.h . */ |
| unsigned local_n_btshft; |
| unsigned local_n_tmask; |
| |
| #define N_BTSHFT local_n_btshft |
| #define N_TMASK local_n_tmask |
| |
| /* Local variables that hold the sizes in the file of various COFF |
| structures. (We only need to know this to read them from the file |
| -- BFD will then translate the data in them, into `internal_xxx' |
| structs in the right byte order, alignment, etc.) */ |
| unsigned local_symesz; |
| unsigned local_auxesz; |
| |
| /* This is set if this is a PE format file. */ |
| bool pe_file; |
| |
| char *stringtab = NULL; |
| long stringtab_length = 0; |
| |
| /* Used when reading coff symbols. */ |
| int symnum; |
| |
| asection *cs_to_bfd_section (coff_symbol *cs); |
| int cs_to_section (coff_symbol *cs); |
| CORE_ADDR cs_section_address (coff_symbol *cs); |
| bool is_import_fixup_symbol (coff_symbol *cs, minimal_symbol_type type); |
| minimal_symbol *record_minimal_symbol (minimal_symbol_reader &reader, |
| coff_symbol *cs, |
| unrelocated_addr address, |
| minimal_symbol_type type, |
| int section); |
| void read_minsyms (file_ptr symtab_offset, unsigned int nsyms); |
| void symtab_read (minimal_symbol_reader &reader, file_ptr symtab_offset, |
| unsigned int nsyms); |
| void read_one_sym (coff_symbol *cs); |
| int init_stringtab (file_ptr offset, |
| gdb::unique_xmalloc_ptr<char> *storage); |
| const char *getsymname (struct internal_syment *symbol_entry); |
| }; |
| |
| /* Return the BFD section that CS points to. */ |
| |
| asection * |
| coff_reader::cs_to_bfd_section (struct coff_symbol *cs) |
| { |
| for (asection *sect : gdb_bfd_sections (symfile_bfd)) |
| if (sect->target_index == cs->c_secnum) |
| return sect; |
| |
| return nullptr; |
| } |
| |
| /* Return the section number (SECT_OFF_*) that CS points to. */ |
| int |
| coff_reader::cs_to_section (struct coff_symbol *cs) |
| { |
| asection *sect = cs_to_bfd_section (cs); |
| |
| if (sect == NULL) |
| return SECT_OFF_TEXT (coffread_objfile); |
| return gdb_bfd_section_index (symfile_bfd, sect); |
| } |
| |
| /* Return the address of the section of a COFF symbol. */ |
| |
| CORE_ADDR |
| coff_reader::cs_section_address (struct coff_symbol *cs) |
| { |
| asection *sect = cs_to_bfd_section (cs); |
| |
| if (sect == nullptr) |
| return 0; |
| |
| return bfd_section_vma (sect); |
| } |
| |
| /* The linker sometimes generates some non-function symbols inside |
| functions referencing variables imported from another DLL. |
| Return true if the given symbol corresponds to one of them. */ |
| |
| bool |
| coff_reader::is_import_fixup_symbol (struct coff_symbol *cs, |
| enum minimal_symbol_type type) |
| { |
| /* The following is a bit of a heuristic using the characteristics |
| of these fixup symbols, but should work well in practice... */ |
| int i; |
| |
| /* Must be a non-static text symbol. */ |
| if (type != mst_text) |
| return false; |
| |
| /* Must be a non-function symbol. */ |
| if (ISFCN (cs->c_type)) |
| return false; |
| |
| /* The name must start with "__fu<digits>__". */ |
| if (!startswith (cs->c_name, "__fu")) |
| return false; |
| if (! c_isdigit (cs->c_name[4])) |
| return false; |
| for (i = 5; cs->c_name[i] != '\0' && c_isdigit (cs->c_name[i]); i++) |
| /* Nothing, just incrementing index past all digits. */; |
| if (cs->c_name[i] != '_' || cs->c_name[i + 1] != '_') |
| return false; |
| |
| return true; |
| } |
| |
| struct minimal_symbol * |
| coff_reader::record_minimal_symbol (minimal_symbol_reader &reader, |
| struct coff_symbol *cs, |
| unrelocated_addr address, |
| enum minimal_symbol_type type, |
| int section) |
| { |
| /* We don't want TDESC entry points in the minimal symbol table. */ |
| if (cs->c_name[0] == '@') |
| return NULL; |
| |
| if (is_import_fixup_symbol (cs, type)) |
| { |
| /* Because the value of these symbols is within a function code |
| range, these symbols interfere with the symbol-from-address |
| reverse lookup; this manifests itself in backtraces, or any |
| other commands that prints symbolic addresses. Just pretend |
| these symbols do not exist. */ |
| return NULL; |
| } |
| |
| return reader.record_full (cs->c_name, true, address, type, section); |
| } |
| |
| /* A helper function for coff_symfile_read that reads minimal |
| symbols. It may also read other forms of symbol as well. */ |
| |
| void |
| coff_reader::read_minsyms (file_ptr symtab_offset, unsigned int nsyms) |
| { |
| /* If minimal symbols were already read, and if we know we aren't |
| going to read any other kind of symbol here, then we can just |
| return. */ |
| if (coffread_objfile->per_bfd->minsyms_read && pe_file && nsyms == 0) |
| return; |
| |
| minimal_symbol_reader reader (coffread_objfile); |
| |
| if (pe_file && nsyms == 0) |
| { |
| /* We've got no debugging symbols, but it's a portable |
| executable, so try to read the export table. */ |
| read_pe_exported_syms (reader, coffread_objfile); |
| } |
| else |
| { |
| /* Now that the executable file is positioned at symbol table, |
| process it and define symbols accordingly. */ |
| symtab_read (reader, symtab_offset, nsyms); |
| } |
| |
| /* Install any minimal symbols that have been collected as the |
| current minimal symbols for this objfile. */ |
| |
| reader.install (); |
| |
| if (pe_file) |
| { |
| for (minimal_symbol *msym : coffread_objfile->msymbols ()) |
| { |
| const char *name = msym->linkage_name (); |
| |
| /* If the minimal symbols whose name are prefixed by "__imp_" |
| or "_imp_", get rid of the prefix, and search the minimal |
| symbol in OBJFILE. Note that 'maintenance print msymbols' |
| shows that type of these "_imp_XXXX" symbols is mst_data. */ |
| if (msym->type () == mst_data) |
| { |
| const char *name1 = NULL; |
| |
| if (startswith (name, "_imp_")) |
| name1 = name + 5; |
| else if (startswith (name, "__imp_")) |
| name1 = name + 6; |
| if (name1 != NULL) |
| { |
| int lead = bfd_get_symbol_leading_char (symfile_bfd); |
| |
| if (lead != '\0' && *name1 == lead) |
| name1 += 1; |
| |
| bound_minimal_symbol found |
| = lookup_minimal_symbol (current_program_space, name1, |
| coffread_objfile); |
| |
| /* If found, there are symbols named "_imp_foo" and "foo" |
| respectively in OBJFILE. Set the type of symbol "foo" |
| as 'mst_solib_trampoline'. */ |
| if (found.minsym != NULL |
| && found.minsym->type () == mst_text) |
| found.minsym->set_type (mst_solib_trampoline); |
| } |
| } |
| } |
| } |
| } |
| |
| /* Read a symbol file, after initialization by coff_symfile_init. */ |
| |
| void |
| coff_reader::symfile_read (symfile_add_flags symfile_flags) |
| { |
| coff_data_type *cdata = coff_data (symfile_bfd); |
| const char *filename = bfd_get_filename (symfile_bfd); |
| int val; |
| unsigned int num_symbols; |
| file_ptr symtab_offset; |
| file_ptr stringtab_offset; |
| |
| /* WARNING WILL ROBINSON! ACCESSING BFD-PRIVATE DATA HERE! FIXME! */ |
| num_symbols = bfd_get_symcount (symfile_bfd); /* How many syms */ |
| symtab_offset = cdata->sym_filepos; /* Symbol table file offset */ |
| stringtab_offset = symtab_offset + /* String table file offset */ |
| num_symbols * cdata->local_symesz; |
| |
| /* Set a few file-statics that give us specific information about |
| the particular COFF file format we're reading. */ |
| local_n_btshft = cdata->local_n_btshft; |
| local_n_tmask = cdata->local_n_tmask; |
| local_symesz = cdata->local_symesz; |
| local_auxesz = cdata->local_auxesz; |
| |
| /* Allocate space for raw symbol and aux entries, based on their |
| space requirements as reported by BFD. */ |
| gdb::def_vector<char> temp_storage (cdata->local_symesz |
| + cdata->local_auxesz); |
| temp_sym = temp_storage.data (); |
| temp_aux = temp_sym + cdata->local_symesz; |
| |
| /* We need to know whether this is a PE file, because in PE files, |
| unlike standard COFF files, symbol values are stored as offsets |
| from the section address, rather than as absolute addresses. |
| FIXME: We should use BFD to read the symbol table, and thus avoid |
| this problem. */ |
| pe_file = (startswith (bfd_get_target (symfile_bfd), "pe") |
| || startswith (bfd_get_target (symfile_bfd), "epoc-pe")); |
| |
| /* End of warning. */ |
| |
| /* Now read the string table, all at once. */ |
| |
| scoped_restore restore_stringtab = make_scoped_restore (&stringtab); |
| gdb::unique_xmalloc_ptr<char> stringtab_storage; |
| val = init_stringtab (stringtab_offset, &stringtab_storage); |
| if (val < 0) |
| error (_("\"%s\": can't get string table"), filename); |
| |
| read_minsyms (symtab_offset, num_symbols); |
| |
| if (!(coffread_objfile->flags & OBJF_READNEVER)) |
| { |
| bool found_stab_section = false; |
| |
| for (asection *sect : gdb_bfd_sections (symfile_bfd)) |
| if (startswith (bfd_section_name (sect), ".stab")) |
| { |
| found_stab_section = true; |
| break; |
| } |
| |
| if (found_stab_section) |
| warning (_ ("stabs debug information is not supported.")); |
| } |
| |
| if (dwarf2_initialize_objfile (coffread_objfile)) |
| { |
| /* Nothing. */ |
| } |
| |
| /* Try to add separate debug file if no symbols table found. */ |
| else if (!coffread_objfile->has_partial_symbols () |
| && coffread_objfile->separate_debug_objfile == NULL |
| && coffread_objfile->separate_debug_objfile_backlink == NULL) |
| { |
| if (coffread_objfile->find_and_add_separate_symbol_file (symfile_flags)) |
| gdb_assert (coffread_objfile->separate_debug_objfile != nullptr); |
| } |
| } |
| |
| static void |
| coff_symfile_read (struct objfile *objfile, symfile_add_flags symfile_flags) |
| { |
| coff_reader reader (objfile); |
| reader.symfile_read (symfile_flags); |
| } |
| |
| /* Given pointers to a symbol table in coff style exec file, |
| analyze them and create struct symtab's describing the symbols. |
| NSYMS is the number of symbols in the symbol table. |
| We read them one at a time using read_one_sym (). */ |
| |
| void |
| coff_reader::symtab_read (minimal_symbol_reader &reader, |
| file_ptr symtab_offset, unsigned int nsyms) |
| { |
| struct gdbarch *gdbarch = coffread_objfile->arch (); |
| struct coff_symbol coff_symbol; |
| struct coff_symbol *cs = &coff_symbol; |
| int val; |
| CORE_ADDR tmpaddr; |
| struct minimal_symbol *msym; |
| |
| symnum = 0; |
| |
| /* Position to read the symbol table. */ |
| val = bfd_seek (symfile_bfd, symtab_offset, 0); |
| if (val < 0) |
| error (_("Error reading symbols from %s: %s"), |
| objfile_name (coffread_objfile), bfd_errmsg (bfd_get_error ())); |
| |
| while (symnum < nsyms) |
| { |
| QUIT; /* Make this command interruptible. */ |
| |
| read_one_sym (cs); |
| |
| /* Typedefs should not be treated as symbol definitions. */ |
| if (ISFCN (cs->c_type) && cs->c_sclass != C_TPDEF) |
| { |
| /* Record all functions -- external and static -- in |
| minsyms. */ |
| int section = cs_to_section (cs); |
| |
| tmpaddr = cs->c_value; |
| /* Don't record unresolved symbols. */ |
| if (!(cs->c_secnum <= 0 && cs->c_value == 0)) |
| record_minimal_symbol (reader, cs, |
| unrelocated_addr (tmpaddr), |
| mst_text, section); |
| |
| continue; |
| } |
| |
| switch (cs->c_sclass) |
| { |
| /* C_LABEL is used for labels and static functions. |
| Including it here allows gdb to see static functions when |
| no debug info is available. */ |
| case C_LABEL: |
| case C_STAT: |
| case C_THUMBLABEL: |
| case C_THUMBSTAT: |
| case C_THUMBSTATFUNC: |
| if (cs->c_name[0] == '.') |
| { |
| /* Flush rest of '.' symbols. */ |
| break; |
| } |
| else if (cs->c_name[0] == 'L' |
| && (startswith (cs->c_name, "LI%") |
| || startswith (cs->c_name, "LF%") |
| || startswith (cs->c_name, "LC%") |
| || startswith (cs->c_name, "LP%") |
| || startswith (cs->c_name, "LPB%") |
| || startswith (cs->c_name, "LBB%") |
| || startswith (cs->c_name, "LBE%") |
| || startswith (cs->c_name, "LPBX%"))) |
| /* At least on a 3b1, gcc generates swbeg and string labels |
| that look like this. Ignore them. */ |
| break; |
| /* For static symbols that don't start with '.'... */ |
| [[fallthrough]]; |
| case C_THUMBEXT: |
| case C_THUMBEXTFUNC: |
| case C_EXT: |
| { |
| /* Record it in the minimal symbols regardless of |
| SDB_TYPE. This parallels what we do for other debug |
| formats, and probably is needed to make |
| print_address_symbolic work right without the (now |
| gone) "set fast-symbolic-addr off" kludge. */ |
| |
| enum minimal_symbol_type ms_type; |
| int sec; |
| |
| if (cs->c_secnum == N_UNDEF) |
| { |
| /* This is a common symbol. We used to rely on |
| the target to tell us whether it knows where |
| the symbol has been relocated to, but none of |
| the target implementations actually provided |
| that operation. So we just ignore the symbol, |
| the same way we would do if we had a target-side |
| symbol lookup which returned no match. */ |
| break; |
| } |
| else if (cs->c_secnum == N_ABS) |
| { |
| /* Use the correct minimal symbol type (and don't |
| relocate) for absolute values. */ |
| ms_type = mst_abs; |
| sec = cs_to_section (cs); |
| tmpaddr = cs->c_value; |
| } |
| else |
| { |
| asection *bfd_section = cs_to_bfd_section (cs); |
| |
| sec = cs_to_section (cs); |
| tmpaddr = cs->c_value; |
| |
| if (bfd_section->flags & SEC_CODE) |
| { |
| ms_type = |
| cs->c_sclass == C_EXT || cs->c_sclass == C_THUMBEXTFUNC |
| || cs->c_sclass == C_THUMBEXT ? |
| mst_text : mst_file_text; |
| tmpaddr = gdbarch_addr_bits_remove (gdbarch, tmpaddr); |
| } |
| else if (bfd_section->flags & SEC_ALLOC |
| && bfd_section->flags & SEC_LOAD) |
| { |
| ms_type = |
| cs->c_sclass == C_EXT || cs->c_sclass == C_THUMBEXT |
| ? mst_data : mst_file_data; |
| } |
| else if (bfd_section->flags & SEC_ALLOC) |
| { |
| ms_type = |
| cs->c_sclass == C_EXT || cs->c_sclass == C_THUMBEXT |
| ? mst_bss : mst_file_bss; |
| } |
| else |
| ms_type = mst_unknown; |
| } |
| |
| msym = record_minimal_symbol (reader, cs, |
| unrelocated_addr (tmpaddr), |
| ms_type, sec); |
| if (msym) |
| gdbarch_coff_make_msymbol_special (gdbarch, |
| cs->c_sclass, msym); |
| } |
| break; |
| } |
| } |
| } |
| |
| /* Routines for reading headers and symbols from executable. */ |
| |
| /* Read the next symbol into CS. */ |
| |
| void |
| coff_reader::read_one_sym (struct coff_symbol *cs) |
| { |
| int i; |
| bfd_size_type bytes; |
| internal_syment sym; |
| |
| bytes = bfd_read (temp_sym, local_symesz, symfile_bfd); |
| if (bytes != local_symesz) |
| error (_("%s: error reading symbols"), objfile_name (coffread_objfile)); |
| bfd_coff_swap_sym_in (symfile_bfd, temp_sym, &sym); |
| cs->c_naux = sym.n_numaux & 0xff; |
| if (cs->c_naux >= 1) |
| { |
| /* We don't need aux entries, read past them. */ |
| for (i = 0; i < cs->c_naux; i++) |
| { |
| bytes = bfd_read (temp_aux, local_auxesz, symfile_bfd); |
| if (bytes != local_auxesz) |
| error (_("%s: error reading symbols"), |
| objfile_name (coffread_objfile)); |
| } |
| } |
| cs->c_name = getsymname (&sym); |
| cs->c_value = sym.n_value; |
| cs->c_sclass = (sym.n_sclass & 0xff); |
| cs->c_secnum = sym.n_scnum; |
| cs->c_type = (unsigned) sym.n_type; |
| |
| symnum += 1 + cs->c_naux; |
| |
| /* The PE file format stores symbol values as offsets within the |
| section, rather than as absolute addresses. We correct that |
| here, if the symbol has an appropriate storage class. FIXME: We |
| should use BFD to read the symbols, rather than duplicating the |
| work here. */ |
| if (pe_file) |
| { |
| switch (cs->c_sclass) |
| { |
| case C_EXT: |
| case C_THUMBEXT: |
| case C_THUMBEXTFUNC: |
| case C_SECTION: |
| case C_NT_WEAK: |
| case C_STAT: |
| case C_THUMBSTAT: |
| case C_THUMBSTATFUNC: |
| case C_LABEL: |
| case C_THUMBLABEL: |
| case C_BLOCK: |
| case C_FCN: |
| case C_EFCN: |
| if (cs->c_secnum != 0) |
| cs->c_value += cs_section_address (cs); |
| break; |
| } |
| } |
| } |
| |
| /* Support for string table handling. */ |
| |
| int |
| coff_reader::init_stringtab (file_ptr offset, |
| gdb::unique_xmalloc_ptr<char> *storage) |
| { |
| long length; |
| int val; |
| unsigned char lengthbuf[4]; |
| |
| /* If the file is stripped, the offset might be zero, indicating no |
| string table. Just return with `stringtab' set to null. */ |
| if (offset == 0) |
| return 0; |
| |
| if (bfd_seek (symfile_bfd, offset, 0) < 0) |
| return -1; |
| |
| val = bfd_read (lengthbuf, sizeof lengthbuf, symfile_bfd); |
| /* If no string table is needed, then the file may end immediately |
| after the symbols. Just return with `stringtab' set to null. */ |
| if (val != sizeof lengthbuf) |
| return 0; |
| length = bfd_h_get_32 (symfile_bfd, lengthbuf); |
| if (length < sizeof lengthbuf) |
| return 0; |
| |
| storage->reset ((char *) xmalloc (length)); |
| stringtab = storage->get (); |
| /* This is in target format (probably not very useful, and not |
| currently used), not host format. */ |
| memcpy (stringtab, lengthbuf, sizeof lengthbuf); |
| stringtab_length = length; |
| if (length == sizeof length) /* Empty table -- just the count. */ |
| return 0; |
| |
| val = bfd_read (stringtab + sizeof lengthbuf, |
| length - sizeof lengthbuf, symfile_bfd); |
| if (val != length - sizeof lengthbuf || stringtab[length - 1] != '\0') |
| return -1; |
| |
| return 0; |
| } |
| |
| const char * |
| coff_reader::getsymname (struct internal_syment *symbol_entry) |
| { |
| static char buffer[SYMNMLEN + 1]; |
| const char *result; |
| |
| if (symbol_entry->_n._n_n._n_zeroes == 0) |
| { |
| if (symbol_entry->_n._n_n._n_offset > stringtab_length) |
| error (_("COFF Error: string table offset (%s) outside string table (length %ld)"), |
| hex_string (symbol_entry->_n._n_n._n_offset), stringtab_length); |
| result = stringtab + symbol_entry->_n._n_n._n_offset; |
| } |
| else |
| { |
| strncpy (buffer, symbol_entry->_n._n_name, SYMNMLEN); |
| buffer[SYMNMLEN] = '\0'; |
| result = buffer; |
| } |
| return result; |
| } |
| |
| /* Register our ability to parse symbols for coff BFD files. */ |
| |
| static const struct sym_fns coff_sym_fns = |
| { |
| [] (objfile *) { }, /* sym_init: read initial info, setup |
| for sym_read() */ |
| coff_symfile_read, /* sym_read: read a symbol file into |
| symtab */ |
| default_symfile_offsets, /* sym_offsets: xlate external to |
| internal form */ |
| default_symfile_segments, /* sym_segments: Get segment |
| information from a file */ |
| |
| default_symfile_relocate, /* sym_relocate: Relocate a debug |
| section. */ |
| NULL, /* sym_probe_fns */ |
| }; |
| |
| INIT_GDB_FILE (coffread) |
| { |
| add_symtab_fns (bfd_target_coff_flavour, &coff_sym_fns); |
| } |