| /* Routines to link ECOFF debugging information. | |
| Copyright (C) 1993-2025 Free Software Foundation, Inc. | |
| Written by Ian Lance Taylor, Cygnus Support, <ian@cygnus.com>. | |
| This file is part of BFD, the Binary File Descriptor library. | |
| 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, write to the Free Software | |
| Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, | |
| MA 02110-1301, USA. */ | |
| #include "sysdep.h" | |
| #include "bfd.h" | |
| #include "bfdlink.h" | |
| #include "libbfd.h" | |
| #include "ecoff-bfd.h" | |
| #include "objalloc.h" | |
| #include "aout/stab_gnu.h" | |
| #include "coff/internal.h" | |
| #include "coff/sym.h" | |
| #include "coff/symconst.h" | |
| #include "coff/ecoff.h" | |
| #include "libcoff.h" | |
| #include "libecoff.h" | |
| /* ECOFF uses two common sections. One is the usual one, and the | |
| other is for small objects. All the small objects are kept | |
| together, and then referenced via the gp pointer, which yields | |
| faster assembler code. This is what we use for the small common | |
| section. */ | |
| static const asymbol ecoff_scom_symbol = | |
| GLOBAL_SYM_INIT (SCOMMON, &_bfd_ecoff_scom_section); | |
| asection _bfd_ecoff_scom_section = | |
| BFD_FAKE_SECTION (_bfd_ecoff_scom_section, &ecoff_scom_symbol, | |
| SCOMMON, 0, SEC_IS_COMMON | SEC_SMALL_DATA); | |
| /* Routines to swap auxiliary information in and out. I am assuming | |
| that the auxiliary information format is always going to be target | |
| independent. */ | |
| /* Swap in a type information record. | |
| BIGEND says whether AUX symbols are big-endian or little-endian; this | |
| info comes from the file header record (fh-fBigendian). */ | |
| void | |
| _bfd_ecoff_swap_tir_in (int bigend, const struct tir_ext *ext_copy, | |
| TIR *intern) | |
| { | |
| struct tir_ext ext[1]; | |
| *ext = *ext_copy; /* Make it reasonable to do in-place. */ | |
| /* now the fun stuff... */ | |
| if (bigend) | |
| { | |
| intern->fBitfield = 0 != (ext->t_bits1[0] & TIR_BITS1_FBITFIELD_BIG); | |
| intern->continued = 0 != (ext->t_bits1[0] & TIR_BITS1_CONTINUED_BIG); | |
| intern->bt = (ext->t_bits1[0] & TIR_BITS1_BT_BIG) | |
| >> TIR_BITS1_BT_SH_BIG; | |
| intern->tq4 = (ext->t_tq45[0] & TIR_BITS_TQ4_BIG) | |
| >> TIR_BITS_TQ4_SH_BIG; | |
| intern->tq5 = (ext->t_tq45[0] & TIR_BITS_TQ5_BIG) | |
| >> TIR_BITS_TQ5_SH_BIG; | |
| intern->tq0 = (ext->t_tq01[0] & TIR_BITS_TQ0_BIG) | |
| >> TIR_BITS_TQ0_SH_BIG; | |
| intern->tq1 = (ext->t_tq01[0] & TIR_BITS_TQ1_BIG) | |
| >> TIR_BITS_TQ1_SH_BIG; | |
| intern->tq2 = (ext->t_tq23[0] & TIR_BITS_TQ2_BIG) | |
| >> TIR_BITS_TQ2_SH_BIG; | |
| intern->tq3 = (ext->t_tq23[0] & TIR_BITS_TQ3_BIG) | |
| >> TIR_BITS_TQ3_SH_BIG; | |
| } | |
| else | |
| { | |
| intern->fBitfield = 0 != (ext->t_bits1[0] & TIR_BITS1_FBITFIELD_LITTLE); | |
| intern->continued = 0 != (ext->t_bits1[0] & TIR_BITS1_CONTINUED_LITTLE); | |
| intern->bt = (ext->t_bits1[0] & TIR_BITS1_BT_LITTLE) | |
| >> TIR_BITS1_BT_SH_LITTLE; | |
| intern->tq4 = (ext->t_tq45[0] & TIR_BITS_TQ4_LITTLE) | |
| >> TIR_BITS_TQ4_SH_LITTLE; | |
| intern->tq5 = (ext->t_tq45[0] & TIR_BITS_TQ5_LITTLE) | |
| >> TIR_BITS_TQ5_SH_LITTLE; | |
| intern->tq0 = (ext->t_tq01[0] & TIR_BITS_TQ0_LITTLE) | |
| >> TIR_BITS_TQ0_SH_LITTLE; | |
| intern->tq1 = (ext->t_tq01[0] & TIR_BITS_TQ1_LITTLE) | |
| >> TIR_BITS_TQ1_SH_LITTLE; | |
| intern->tq2 = (ext->t_tq23[0] & TIR_BITS_TQ2_LITTLE) | |
| >> TIR_BITS_TQ2_SH_LITTLE; | |
| intern->tq3 = (ext->t_tq23[0] & TIR_BITS_TQ3_LITTLE) | |
| >> TIR_BITS_TQ3_SH_LITTLE; | |
| } | |
| #ifdef TEST | |
| if (memcmp ((char *)ext, (char *)intern, sizeof (*intern)) != 0) | |
| abort (); | |
| #endif | |
| } | |
| /* Swap out a type information record. | |
| BIGEND says whether AUX symbols are big-endian or little-endian; this | |
| info comes from the file header record (fh-fBigendian). */ | |
| void | |
| _bfd_ecoff_swap_tir_out (int bigend, | |
| const TIR *intern_copy, | |
| struct tir_ext *ext) | |
| { | |
| TIR intern[1]; | |
| *intern = *intern_copy; /* Make it reasonable to do in-place. */ | |
| /* now the fun stuff... */ | |
| if (bigend) | |
| { | |
| ext->t_bits1[0] = ((intern->fBitfield ? TIR_BITS1_FBITFIELD_BIG : 0) | |
| | (intern->continued ? TIR_BITS1_CONTINUED_BIG : 0) | |
| | ((intern->bt << TIR_BITS1_BT_SH_BIG) | |
| & TIR_BITS1_BT_BIG)); | |
| ext->t_tq45[0] = (((intern->tq4 << TIR_BITS_TQ4_SH_BIG) | |
| & TIR_BITS_TQ4_BIG) | |
| | ((intern->tq5 << TIR_BITS_TQ5_SH_BIG) | |
| & TIR_BITS_TQ5_BIG)); | |
| ext->t_tq01[0] = (((intern->tq0 << TIR_BITS_TQ0_SH_BIG) | |
| & TIR_BITS_TQ0_BIG) | |
| | ((intern->tq1 << TIR_BITS_TQ1_SH_BIG) | |
| & TIR_BITS_TQ1_BIG)); | |
| ext->t_tq23[0] = (((intern->tq2 << TIR_BITS_TQ2_SH_BIG) | |
| & TIR_BITS_TQ2_BIG) | |
| | ((intern->tq3 << TIR_BITS_TQ3_SH_BIG) | |
| & TIR_BITS_TQ3_BIG)); | |
| } | |
| else | |
| { | |
| ext->t_bits1[0] = ((intern->fBitfield ? TIR_BITS1_FBITFIELD_LITTLE : 0) | |
| | (intern->continued ? TIR_BITS1_CONTINUED_LITTLE : 0) | |
| | ((intern->bt << TIR_BITS1_BT_SH_LITTLE) | |
| & TIR_BITS1_BT_LITTLE)); | |
| ext->t_tq45[0] = (((intern->tq4 << TIR_BITS_TQ4_SH_LITTLE) | |
| & TIR_BITS_TQ4_LITTLE) | |
| | ((intern->tq5 << TIR_BITS_TQ5_SH_LITTLE) | |
| & TIR_BITS_TQ5_LITTLE)); | |
| ext->t_tq01[0] = (((intern->tq0 << TIR_BITS_TQ0_SH_LITTLE) | |
| & TIR_BITS_TQ0_LITTLE) | |
| | ((intern->tq1 << TIR_BITS_TQ1_SH_LITTLE) | |
| & TIR_BITS_TQ1_LITTLE)); | |
| ext->t_tq23[0] = (((intern->tq2 << TIR_BITS_TQ2_SH_LITTLE) | |
| & TIR_BITS_TQ2_LITTLE) | |
| | ((intern->tq3 << TIR_BITS_TQ3_SH_LITTLE) | |
| & TIR_BITS_TQ3_LITTLE)); | |
| } | |
| #ifdef TEST | |
| if (memcmp ((char *)ext, (char *)intern, sizeof (*intern)) != 0) | |
| abort (); | |
| #endif | |
| } | |
| /* Swap in a relative symbol record. BIGEND says whether it is in | |
| big-endian or little-endian format.*/ | |
| void | |
| _bfd_ecoff_swap_rndx_in (int bigend, | |
| const struct rndx_ext *ext_copy, | |
| RNDXR *intern) | |
| { | |
| struct rndx_ext ext[1]; | |
| *ext = *ext_copy; /* Make it reasonable to do in-place. */ | |
| /* now the fun stuff... */ | |
| if (bigend) | |
| { | |
| intern->rfd = (ext->r_bits[0] << RNDX_BITS0_RFD_SH_LEFT_BIG) | |
| | ((ext->r_bits[1] & RNDX_BITS1_RFD_BIG) | |
| >> RNDX_BITS1_RFD_SH_BIG); | |
| intern->index = ((ext->r_bits[1] & RNDX_BITS1_INDEX_BIG) | |
| << RNDX_BITS1_INDEX_SH_LEFT_BIG) | |
| | (ext->r_bits[2] << RNDX_BITS2_INDEX_SH_LEFT_BIG) | |
| | (ext->r_bits[3] << RNDX_BITS3_INDEX_SH_LEFT_BIG); | |
| } | |
| else | |
| { | |
| intern->rfd = (ext->r_bits[0] << RNDX_BITS0_RFD_SH_LEFT_LITTLE) | |
| | ((ext->r_bits[1] & RNDX_BITS1_RFD_LITTLE) | |
| << RNDX_BITS1_RFD_SH_LEFT_LITTLE); | |
| intern->index = ((ext->r_bits[1] & RNDX_BITS1_INDEX_LITTLE) | |
| >> RNDX_BITS1_INDEX_SH_LITTLE) | |
| | (ext->r_bits[2] << RNDX_BITS2_INDEX_SH_LEFT_LITTLE) | |
| | ((unsigned int) ext->r_bits[3] | |
| << RNDX_BITS3_INDEX_SH_LEFT_LITTLE); | |
| } | |
| #ifdef TEST | |
| if (memcmp ((char *)ext, (char *)intern, sizeof (*intern)) != 0) | |
| abort (); | |
| #endif | |
| } | |
| /* Swap out a relative symbol record. BIGEND says whether it is in | |
| big-endian or little-endian format.*/ | |
| void | |
| _bfd_ecoff_swap_rndx_out (int bigend, | |
| const RNDXR *intern_copy, | |
| struct rndx_ext *ext) | |
| { | |
| RNDXR intern[1]; | |
| *intern = *intern_copy; /* Make it reasonable to do in-place. */ | |
| /* now the fun stuff... */ | |
| if (bigend) | |
| { | |
| ext->r_bits[0] = intern->rfd >> RNDX_BITS0_RFD_SH_LEFT_BIG; | |
| ext->r_bits[1] = (((intern->rfd << RNDX_BITS1_RFD_SH_BIG) | |
| & RNDX_BITS1_RFD_BIG) | |
| | ((intern->index >> RNDX_BITS1_INDEX_SH_LEFT_BIG) | |
| & RNDX_BITS1_INDEX_BIG)); | |
| ext->r_bits[2] = intern->index >> RNDX_BITS2_INDEX_SH_LEFT_BIG; | |
| ext->r_bits[3] = intern->index >> RNDX_BITS3_INDEX_SH_LEFT_BIG; | |
| } | |
| else | |
| { | |
| ext->r_bits[0] = intern->rfd >> RNDX_BITS0_RFD_SH_LEFT_LITTLE; | |
| ext->r_bits[1] = (((intern->rfd >> RNDX_BITS1_RFD_SH_LEFT_LITTLE) | |
| & RNDX_BITS1_RFD_LITTLE) | |
| | ((intern->index << RNDX_BITS1_INDEX_SH_LITTLE) | |
| & RNDX_BITS1_INDEX_LITTLE)); | |
| ext->r_bits[2] = intern->index >> RNDX_BITS2_INDEX_SH_LEFT_LITTLE; | |
| ext->r_bits[3] = intern->index >> RNDX_BITS3_INDEX_SH_LEFT_LITTLE; | |
| } | |
| #ifdef TEST | |
| if (memcmp ((char *)ext, (char *)intern, sizeof (*intern)) != 0) | |
| abort (); | |
| #endif | |
| } | |
| /* The minimum amount of data to allocate. */ | |
| #define ALLOC_SIZE (4064) | |
| /* Add bytes to a buffer. Return success. */ | |
| static bool | |
| ecoff_add_bytes (char **buf, char **bufend, size_t need) | |
| { | |
| size_t have; | |
| size_t want; | |
| char *newbuf; | |
| have = *bufend - *buf; | |
| if (have > need) | |
| want = ALLOC_SIZE; | |
| else | |
| { | |
| want = need - have; | |
| if (want < ALLOC_SIZE) | |
| want = ALLOC_SIZE; | |
| } | |
| newbuf = (char *) bfd_realloc (*buf, (bfd_size_type) have + want); | |
| if (newbuf == NULL) | |
| return false; | |
| *buf = newbuf; | |
| *bufend = *buf + have + want; | |
| return true; | |
| } | |
| /* We keep a hash table which maps strings to numbers. We use it to | |
| map FDR names to indices in the output file, and to map local | |
| strings when combining stabs debugging information. */ | |
| struct string_hash_entry | |
| { | |
| struct bfd_hash_entry root; | |
| /* FDR index or string table offset. */ | |
| long val; | |
| /* Next entry in string table. */ | |
| struct string_hash_entry *next; | |
| }; | |
| struct string_hash_table | |
| { | |
| struct bfd_hash_table table; | |
| }; | |
| /* Routine to create an entry in a string hash table. */ | |
| static struct bfd_hash_entry * | |
| string_hash_newfunc (struct bfd_hash_entry *entry, | |
| struct bfd_hash_table *table, | |
| const char *string) | |
| { | |
| struct string_hash_entry *ret = (struct string_hash_entry *) entry; | |
| /* Allocate the structure if it has not already been allocated by a | |
| subclass. */ | |
| if (ret == (struct string_hash_entry *) NULL) | |
| ret = ((struct string_hash_entry *) | |
| bfd_hash_allocate (table, sizeof (struct string_hash_entry))); | |
| if (ret == (struct string_hash_entry *) NULL) | |
| return NULL; | |
| /* Call the allocation method of the superclass. */ | |
| ret = ((struct string_hash_entry *) | |
| bfd_hash_newfunc ((struct bfd_hash_entry *) ret, table, string)); | |
| if (ret) | |
| { | |
| /* Initialize the local fields. */ | |
| ret->val = -1; | |
| ret->next = NULL; | |
| } | |
| return (struct bfd_hash_entry *) ret; | |
| } | |
| /* Look up an entry in an string hash table. */ | |
| #define string_hash_lookup(t, string, create, copy) \ | |
| ((struct string_hash_entry *) \ | |
| bfd_hash_lookup (&(t)->table, (string), (create), (copy))) | |
| /* We can't afford to read in all the debugging information when we do | |
| a link. Instead, we build a list of these structures to show how | |
| different parts of the input file map to the output file. */ | |
| struct shuffle | |
| { | |
| /* The next entry in this linked list. */ | |
| struct shuffle *next; | |
| /* The length of the information. */ | |
| unsigned long size; | |
| /* Whether this information comes from a file or not. */ | |
| bool filep; | |
| union | |
| { | |
| struct | |
| { | |
| /* The BFD the data comes from. */ | |
| bfd *input_bfd; | |
| /* The offset within input_bfd. */ | |
| file_ptr offset; | |
| } file; | |
| /* The data to be written out. */ | |
| void * memory; | |
| } u; | |
| }; | |
| /* This structure holds information across calls to | |
| bfd_ecoff_debug_accumulate. */ | |
| struct accumulate | |
| { | |
| /* The FDR hash table. */ | |
| struct string_hash_table fdr_hash; | |
| /* The strings hash table. */ | |
| struct string_hash_table str_hash; | |
| /* Linked lists describing how to shuffle the input debug | |
| information into the output file. We keep a pointer to both the | |
| head and the tail. */ | |
| struct shuffle *line; | |
| struct shuffle *line_end; | |
| struct shuffle *pdr; | |
| struct shuffle *pdr_end; | |
| struct shuffle *sym; | |
| struct shuffle *sym_end; | |
| struct shuffle *opt; | |
| struct shuffle *opt_end; | |
| struct shuffle *aux; | |
| struct shuffle *aux_end; | |
| struct shuffle *ss; | |
| struct shuffle *ss_end; | |
| struct string_hash_entry *ss_hash; | |
| struct string_hash_entry *ss_hash_end; | |
| struct shuffle *fdr; | |
| struct shuffle *fdr_end; | |
| struct shuffle *rfd; | |
| struct shuffle *rfd_end; | |
| /* The size of the largest file shuffle. */ | |
| unsigned long largest_file_shuffle; | |
| /* An objalloc for debugging information. */ | |
| struct objalloc *memory; | |
| }; | |
| /* Add a file entry to a shuffle list. */ | |
| static bool | |
| add_file_shuffle (struct accumulate *ainfo, | |
| struct shuffle **head, | |
| struct shuffle **tail, | |
| bfd *input_bfd, | |
| file_ptr offset, | |
| unsigned long size) | |
| { | |
| struct shuffle *n; | |
| if (*tail != (struct shuffle *) NULL | |
| && (*tail)->filep | |
| && (*tail)->u.file.input_bfd == input_bfd | |
| && (*tail)->u.file.offset + (*tail)->size == (unsigned long) offset) | |
| { | |
| /* Just merge this entry onto the existing one. */ | |
| (*tail)->size += size; | |
| if ((*tail)->size > ainfo->largest_file_shuffle) | |
| ainfo->largest_file_shuffle = (*tail)->size; | |
| return true; | |
| } | |
| n = (struct shuffle *) objalloc_alloc (ainfo->memory, | |
| sizeof (struct shuffle)); | |
| if (!n) | |
| { | |
| bfd_set_error (bfd_error_no_memory); | |
| return false; | |
| } | |
| n->next = NULL; | |
| n->size = size; | |
| n->filep = true; | |
| n->u.file.input_bfd = input_bfd; | |
| n->u.file.offset = offset; | |
| if (*head == (struct shuffle *) NULL) | |
| *head = n; | |
| if (*tail != (struct shuffle *) NULL) | |
| (*tail)->next = n; | |
| *tail = n; | |
| if (size > ainfo->largest_file_shuffle) | |
| ainfo->largest_file_shuffle = size; | |
| return true; | |
| } | |
| /* Add a memory entry to a shuffle list. */ | |
| static bool | |
| add_memory_shuffle (struct accumulate *ainfo, | |
| struct shuffle **head, | |
| struct shuffle **tail, | |
| bfd_byte *data, | |
| unsigned long size) | |
| { | |
| struct shuffle *n; | |
| n = (struct shuffle *) objalloc_alloc (ainfo->memory, | |
| sizeof (struct shuffle)); | |
| if (!n) | |
| { | |
| bfd_set_error (bfd_error_no_memory); | |
| return false; | |
| } | |
| n->next = NULL; | |
| n->size = size; | |
| n->filep = false; | |
| n->u.memory = data; | |
| if (*head == (struct shuffle *) NULL) | |
| *head = n; | |
| if (*tail != (struct shuffle *) NULL) | |
| (*tail)->next = n; | |
| *tail = n; | |
| return true; | |
| } | |
| /* Initialize the FDR hash table. This returns a handle which is then | |
| passed in to bfd_ecoff_debug_accumulate, et. al. */ | |
| void * | |
| bfd_ecoff_debug_init (bfd *output_bfd ATTRIBUTE_UNUSED, | |
| struct ecoff_debug_info *output_debug, | |
| const struct ecoff_debug_swap *output_swap ATTRIBUTE_UNUSED, | |
| struct bfd_link_info *info) | |
| { | |
| struct accumulate *ainfo; | |
| size_t amt = sizeof (struct accumulate); | |
| ainfo = (struct accumulate *) bfd_malloc (amt); | |
| if (!ainfo) | |
| return NULL; | |
| if (!bfd_hash_table_init_n (&ainfo->fdr_hash.table, string_hash_newfunc, | |
| sizeof (struct string_hash_entry), 1021)) | |
| return NULL; | |
| ainfo->line = NULL; | |
| ainfo->line_end = NULL; | |
| ainfo->pdr = NULL; | |
| ainfo->pdr_end = NULL; | |
| ainfo->sym = NULL; | |
| ainfo->sym_end = NULL; | |
| ainfo->opt = NULL; | |
| ainfo->opt_end = NULL; | |
| ainfo->aux = NULL; | |
| ainfo->aux_end = NULL; | |
| ainfo->ss = NULL; | |
| ainfo->ss_end = NULL; | |
| ainfo->ss_hash = NULL; | |
| ainfo->ss_hash_end = NULL; | |
| ainfo->fdr = NULL; | |
| ainfo->fdr_end = NULL; | |
| ainfo->rfd = NULL; | |
| ainfo->rfd_end = NULL; | |
| ainfo->largest_file_shuffle = 0; | |
| if (! bfd_link_relocatable (info)) | |
| { | |
| if (!bfd_hash_table_init (&ainfo->str_hash.table, string_hash_newfunc, | |
| sizeof (struct string_hash_entry))) | |
| return NULL; | |
| /* The first entry in the string table is the empty string. */ | |
| output_debug->symbolic_header.issMax = 1; | |
| } | |
| ainfo->memory = objalloc_create (); | |
| if (ainfo->memory == NULL) | |
| { | |
| bfd_set_error (bfd_error_no_memory); | |
| return NULL; | |
| } | |
| return ainfo; | |
| } | |
| /* Free the accumulated debugging information. */ | |
| void | |
| bfd_ecoff_debug_free (void * handle, | |
| bfd *output_bfd ATTRIBUTE_UNUSED, | |
| struct ecoff_debug_info *output_debug ATTRIBUTE_UNUSED, | |
| const struct ecoff_debug_swap *output_swap ATTRIBUTE_UNUSED, | |
| struct bfd_link_info *info) | |
| { | |
| struct accumulate *ainfo = (struct accumulate *) handle; | |
| bfd_hash_table_free (&ainfo->fdr_hash.table); | |
| if (! bfd_link_relocatable (info)) | |
| bfd_hash_table_free (&ainfo->str_hash.table); | |
| objalloc_free (ainfo->memory); | |
| free (ainfo); | |
| } | |
| /* Accumulate the debugging information from INPUT_BFD into | |
| OUTPUT_BFD. The INPUT_DEBUG argument points to some ECOFF | |
| debugging information which we want to link into the information | |
| pointed to by the OUTPUT_DEBUG argument. OUTPUT_SWAP and | |
| INPUT_SWAP point to the swapping information needed. INFO is the | |
| linker information structure. HANDLE is returned by | |
| bfd_ecoff_debug_init. */ | |
| bool | |
| bfd_ecoff_debug_accumulate (void * handle, | |
| bfd *output_bfd, | |
| struct ecoff_debug_info *output_debug, | |
| const struct ecoff_debug_swap *output_swap, | |
| bfd *input_bfd, | |
| struct ecoff_debug_info *input_debug, | |
| const struct ecoff_debug_swap *input_swap, | |
| struct bfd_link_info *info) | |
| { | |
| struct accumulate *ainfo = (struct accumulate *) handle; | |
| void (* const swap_sym_in) (bfd *, void *, SYMR *) | |
| = input_swap->swap_sym_in; | |
| void (* const swap_rfd_in) (bfd *, void *, RFDT *) | |
| = input_swap->swap_rfd_in; | |
| void (* const swap_sym_out) (bfd *, const SYMR *, void *) | |
| = output_swap->swap_sym_out; | |
| void (* const swap_fdr_out) (bfd *, const FDR *, void *) | |
| = output_swap->swap_fdr_out; | |
| void (* const swap_rfd_out) (bfd *, const RFDT *, void *) | |
| = output_swap->swap_rfd_out; | |
| bfd_size_type external_pdr_size = output_swap->external_pdr_size; | |
| bfd_size_type external_sym_size = output_swap->external_sym_size; | |
| bfd_size_type external_opt_size = output_swap->external_opt_size; | |
| bfd_size_type external_fdr_size = output_swap->external_fdr_size; | |
| bfd_size_type external_rfd_size = output_swap->external_rfd_size; | |
| HDRR * const output_symhdr = &output_debug->symbolic_header; | |
| HDRR * const input_symhdr = &input_debug->symbolic_header; | |
| bfd_vma section_adjust[scMax]; | |
| asection *sec; | |
| bfd_byte *fdr_start; | |
| bfd_byte *fdr_ptr; | |
| bfd_byte *fdr_end; | |
| bfd_size_type fdr_add; | |
| unsigned int copied; | |
| RFDT i; | |
| unsigned long sz; | |
| bfd_byte *rfd_out; | |
| bfd_byte *rfd_in; | |
| bfd_byte *rfd_end; | |
| long newrfdbase = 0; | |
| long oldrfdbase = 0; | |
| bfd_byte *fdr_out; | |
| bfd_size_type amt; | |
| /* Use section_adjust to hold the value to add to a symbol in a | |
| particular section. */ | |
| memset (section_adjust, 0, sizeof section_adjust); | |
| #define SET(name, indx) \ | |
| sec = bfd_get_section_by_name (input_bfd, name); \ | |
| if (sec != NULL) \ | |
| section_adjust[indx] = (sec->output_section->vma \ | |
| + sec->output_offset \ | |
| - sec->vma); | |
| SET (".text", scText); | |
| SET (".data", scData); | |
| SET (".bss", scBss); | |
| SET (".sdata", scSData); | |
| SET (".sbss", scSBss); | |
| /* scRdata section may be either .rdata or .rodata. */ | |
| SET (".rdata", scRData); | |
| SET (".rodata", scRData); | |
| SET (".init", scInit); | |
| SET (".fini", scFini); | |
| SET (".rconst", scRConst); | |
| #undef SET | |
| /* Find all the debugging information based on the FDR's. We need | |
| to handle them whether they are swapped or not. */ | |
| if (input_debug->fdr != (FDR *) NULL) | |
| { | |
| fdr_start = (bfd_byte *) input_debug->fdr; | |
| fdr_add = sizeof (FDR); | |
| } | |
| else | |
| { | |
| fdr_start = (bfd_byte *) input_debug->external_fdr; | |
| fdr_add = input_swap->external_fdr_size; | |
| } | |
| fdr_end = fdr_start + input_symhdr->ifdMax * fdr_add; | |
| amt = input_symhdr->ifdMax; | |
| amt *= sizeof (RFDT); | |
| input_debug->ifdmap = (RFDT *) bfd_alloc (input_bfd, amt); | |
| sz = (input_symhdr->crfd + input_symhdr->ifdMax) * external_rfd_size; | |
| rfd_out = (bfd_byte *) objalloc_alloc (ainfo->memory, sz); | |
| if (!input_debug->ifdmap || !rfd_out) | |
| { | |
| bfd_set_error (bfd_error_no_memory); | |
| return false; | |
| } | |
| if (!add_memory_shuffle (ainfo, &ainfo->rfd, &ainfo->rfd_end, rfd_out, sz)) | |
| return false; | |
| copied = 0; | |
| /* Look through the FDR's to see which ones we are going to include | |
| in the final output. We do not want duplicate FDR information | |
| for header files, because ECOFF debugging is often very large. | |
| When we find an FDR with no line information which can be merged, | |
| we look it up in a hash table to ensure that we only include it | |
| once. We keep a table mapping FDR numbers to the final number | |
| they get with the BFD, so that we can refer to it when we write | |
| out the external symbols. */ | |
| for (fdr_ptr = fdr_start, i = 0; | |
| fdr_ptr < fdr_end; | |
| fdr_ptr += fdr_add, i++, rfd_out += external_rfd_size) | |
| { | |
| FDR fdr; | |
| if (input_debug->fdr != (FDR *) NULL) | |
| fdr = *(FDR *) fdr_ptr; | |
| else | |
| (*input_swap->swap_fdr_in) (input_bfd, fdr_ptr, &fdr); | |
| /* See if this FDR can be merged with an existing one. */ | |
| if (fdr.cbLine == 0 && fdr.rss != -1 && fdr.fMerge) | |
| { | |
| const char *name; | |
| char *lookup; | |
| struct string_hash_entry *fh; | |
| /* We look up a string formed from the file name and the | |
| number of symbols and aux entries. Sometimes an include | |
| file will conditionally define a typedef or something | |
| based on the order of include files. Using the number of | |
| symbols and aux entries as a hash reduces the chance that | |
| we will merge symbol information that should not be | |
| merged. */ | |
| name = input_debug->ss + fdr.issBase + fdr.rss; | |
| lookup = (char *) bfd_malloc ((bfd_size_type) strlen (name) + 20); | |
| if (lookup == NULL) | |
| return false; | |
| sprintf (lookup, "%s %lx %lx", name, (unsigned long) fdr.csym, | |
| (unsigned long) fdr.caux); | |
| fh = string_hash_lookup (&ainfo->fdr_hash, lookup, true, true); | |
| free (lookup); | |
| if (fh == (struct string_hash_entry *) NULL) | |
| return false; | |
| if (fh->val != -1) | |
| { | |
| input_debug->ifdmap[i] = fh->val; | |
| (*swap_rfd_out) (output_bfd, input_debug->ifdmap + i, rfd_out); | |
| /* Don't copy this FDR. */ | |
| continue; | |
| } | |
| fh->val = output_symhdr->ifdMax + copied; | |
| } | |
| input_debug->ifdmap[i] = output_symhdr->ifdMax + copied; | |
| (*swap_rfd_out) (output_bfd, input_debug->ifdmap + i, rfd_out); | |
| ++copied; | |
| } | |
| newrfdbase = output_symhdr->crfd; | |
| output_symhdr->crfd += input_symhdr->ifdMax; | |
| /* Copy over any existing RFD's. RFD's are only created by the | |
| linker, so this will only happen for input files which are the | |
| result of a partial link. */ | |
| rfd_in = (bfd_byte *) input_debug->external_rfd; | |
| rfd_end = rfd_in + input_symhdr->crfd * input_swap->external_rfd_size; | |
| for (; | |
| rfd_in < rfd_end; | |
| rfd_in += input_swap->external_rfd_size) | |
| { | |
| RFDT rfd; | |
| (*swap_rfd_in) (input_bfd, rfd_in, &rfd); | |
| BFD_ASSERT (rfd >= 0 && rfd < input_symhdr->ifdMax); | |
| rfd = input_debug->ifdmap[rfd]; | |
| (*swap_rfd_out) (output_bfd, &rfd, rfd_out); | |
| rfd_out += external_rfd_size; | |
| } | |
| oldrfdbase = output_symhdr->crfd; | |
| output_symhdr->crfd += input_symhdr->crfd; | |
| /* Look through the FDR's and copy over all associated debugging | |
| information. */ | |
| sz = copied * external_fdr_size; | |
| fdr_out = (bfd_byte *) objalloc_alloc (ainfo->memory, sz); | |
| if (!fdr_out) | |
| { | |
| bfd_set_error (bfd_error_no_memory); | |
| return false; | |
| } | |
| if (!add_memory_shuffle (ainfo, &ainfo->fdr, &ainfo->fdr_end, fdr_out, sz)) | |
| return false; | |
| for (fdr_ptr = fdr_start, i = 0; | |
| fdr_ptr < fdr_end; | |
| fdr_ptr += fdr_add, i++) | |
| { | |
| FDR fdr; | |
| bfd_byte *sym_out; | |
| bfd_byte *lraw_src; | |
| bfd_byte *lraw_end; | |
| bool fgotfilename; | |
| if (input_debug->ifdmap[i] < output_symhdr->ifdMax) | |
| { | |
| /* We are not copying this FDR. */ | |
| continue; | |
| } | |
| if (input_debug->fdr != (FDR *) NULL) | |
| fdr = *(FDR *) fdr_ptr; | |
| else | |
| (*input_swap->swap_fdr_in) (input_bfd, fdr_ptr, &fdr); | |
| /* FIXME: It is conceivable that this FDR points to the .init or | |
| .fini section, in which case this will not do the right | |
| thing. */ | |
| fdr.adr += section_adjust[scText]; | |
| /* Swap in the local symbols, adjust their values, and swap them | |
| out again. */ | |
| fgotfilename = false; | |
| sz = fdr.csym * external_sym_size; | |
| sym_out = (bfd_byte *) objalloc_alloc (ainfo->memory, sz); | |
| if (!sym_out) | |
| { | |
| bfd_set_error (bfd_error_no_memory); | |
| return false; | |
| } | |
| if (!add_memory_shuffle (ainfo, &ainfo->sym, &ainfo->sym_end, sym_out, | |
| sz)) | |
| return false; | |
| lraw_src = ((bfd_byte *) input_debug->external_sym | |
| + fdr.isymBase * input_swap->external_sym_size); | |
| lraw_end = lraw_src + fdr.csym * input_swap->external_sym_size; | |
| for (; lraw_src < lraw_end; lraw_src += input_swap->external_sym_size) | |
| { | |
| SYMR internal_sym; | |
| (*swap_sym_in) (input_bfd, lraw_src, &internal_sym); | |
| BFD_ASSERT (internal_sym.sc != scCommon | |
| && internal_sym.sc != scSCommon); | |
| /* Adjust the symbol value if appropriate. */ | |
| switch (internal_sym.st) | |
| { | |
| case stNil: | |
| if (ECOFF_IS_STAB (&internal_sym)) | |
| break; | |
| /* Fall through. */ | |
| case stGlobal: | |
| case stStatic: | |
| case stLabel: | |
| case stProc: | |
| case stStaticProc: | |
| internal_sym.value += section_adjust[internal_sym.sc]; | |
| break; | |
| default: | |
| break; | |
| } | |
| /* If we are doing a final link, we hash all the strings in | |
| the local symbol table together. This reduces the amount | |
| of space required by debugging information. We don't do | |
| this when performing a relocatable link because it would | |
| prevent us from easily merging different FDR's. */ | |
| if (! bfd_link_relocatable (info)) | |
| { | |
| bool ffilename; | |
| const char *name; | |
| if (! fgotfilename && internal_sym.iss == fdr.rss) | |
| ffilename = true; | |
| else | |
| ffilename = false; | |
| /* Hash the name into the string table. */ | |
| name = input_debug->ss + fdr.issBase + internal_sym.iss; | |
| if (*name == '\0') | |
| internal_sym.iss = 0; | |
| else | |
| { | |
| struct string_hash_entry *sh; | |
| sh = string_hash_lookup (&ainfo->str_hash, name, true, true); | |
| if (sh == (struct string_hash_entry *) NULL) | |
| return false; | |
| if (sh->val == -1) | |
| { | |
| sh->val = output_symhdr->issMax; | |
| output_symhdr->issMax += strlen (name) + 1; | |
| if (ainfo->ss_hash == (struct string_hash_entry *) NULL) | |
| ainfo->ss_hash = sh; | |
| if (ainfo->ss_hash_end | |
| != (struct string_hash_entry *) NULL) | |
| ainfo->ss_hash_end->next = sh; | |
| ainfo->ss_hash_end = sh; | |
| } | |
| internal_sym.iss = sh->val; | |
| } | |
| if (ffilename) | |
| { | |
| fdr.rss = internal_sym.iss; | |
| fgotfilename = true; | |
| } | |
| } | |
| (*swap_sym_out) (output_bfd, &internal_sym, sym_out); | |
| sym_out += external_sym_size; | |
| } | |
| fdr.isymBase = output_symhdr->isymMax; | |
| output_symhdr->isymMax += fdr.csym; | |
| /* Copy the information that does not need swapping. */ | |
| /* FIXME: If we are relaxing, we need to adjust the line | |
| numbers. Frankly, forget it. Anybody using stabs debugging | |
| information will not use this line number information, and | |
| stabs are adjusted correctly. */ | |
| if (fdr.cbLine > 0) | |
| { | |
| file_ptr pos = input_symhdr->cbLineOffset + fdr.cbLineOffset; | |
| if (!add_file_shuffle (ainfo, &ainfo->line, &ainfo->line_end, | |
| input_bfd, pos, (unsigned long) fdr.cbLine)) | |
| return false; | |
| fdr.ilineBase = output_symhdr->ilineMax; | |
| fdr.cbLineOffset = output_symhdr->cbLine; | |
| output_symhdr->ilineMax += fdr.cline; | |
| output_symhdr->cbLine += fdr.cbLine; | |
| } | |
| if (fdr.caux > 0) | |
| { | |
| file_ptr pos = (input_symhdr->cbAuxOffset | |
| + fdr.iauxBase * sizeof (union aux_ext)); | |
| if (!add_file_shuffle (ainfo, &ainfo->aux, &ainfo->aux_end, | |
| input_bfd, pos, | |
| fdr.caux * sizeof (union aux_ext))) | |
| return false; | |
| fdr.iauxBase = output_symhdr->iauxMax; | |
| output_symhdr->iauxMax += fdr.caux; | |
| } | |
| if (! bfd_link_relocatable (info)) | |
| { | |
| /* When we are hashing strings, we lie about the number of | |
| strings attached to each FDR. We need to set cbSs | |
| because some versions of dbx apparently use it to decide | |
| how much of the string table to read in. */ | |
| fdr.issBase = 0; | |
| fdr.cbSs = output_symhdr->issMax; | |
| } | |
| else if (fdr.cbSs > 0) | |
| { | |
| file_ptr pos = input_symhdr->cbSsOffset + fdr.issBase; | |
| if (!add_file_shuffle (ainfo, &ainfo->ss, &ainfo->ss_end, | |
| input_bfd, pos, (unsigned long) fdr.cbSs)) | |
| return false; | |
| fdr.issBase = output_symhdr->issMax; | |
| output_symhdr->issMax += fdr.cbSs; | |
| } | |
| if (output_bfd->xvec->header_byteorder | |
| == input_bfd->xvec->header_byteorder) | |
| { | |
| /* The two BFD's have the same endianness, and we don't have | |
| to adjust the PDR addresses, so simply copying the | |
| information will suffice. */ | |
| BFD_ASSERT (external_pdr_size == input_swap->external_pdr_size); | |
| if (fdr.cpd > 0) | |
| { | |
| file_ptr pos = (input_symhdr->cbPdOffset | |
| + fdr.ipdFirst * external_pdr_size); | |
| unsigned long size = fdr.cpd * external_pdr_size; | |
| if (!add_file_shuffle (ainfo, &ainfo->pdr, &ainfo->pdr_end, | |
| input_bfd, pos, size)) | |
| return false; | |
| } | |
| BFD_ASSERT (external_opt_size == input_swap->external_opt_size); | |
| if (fdr.copt > 0) | |
| { | |
| file_ptr pos = (input_symhdr->cbOptOffset | |
| + fdr.ioptBase * external_opt_size); | |
| unsigned long size = fdr.copt * external_opt_size; | |
| if (!add_file_shuffle (ainfo, &ainfo->opt, &ainfo->opt_end, | |
| input_bfd, pos, size)) | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| bfd_size_type outsz, insz; | |
| bfd_byte *in; | |
| bfd_byte *end; | |
| bfd_byte *out; | |
| /* The two BFD's have different endianness, so we must swap | |
| everything in and out. This code would always work, but | |
| it would be unnecessarily slow in the normal case. */ | |
| outsz = external_pdr_size; | |
| insz = input_swap->external_pdr_size; | |
| in = ((bfd_byte *) input_debug->external_pdr | |
| + fdr.ipdFirst * insz); | |
| end = in + fdr.cpd * insz; | |
| sz = fdr.cpd * outsz; | |
| out = (bfd_byte *) objalloc_alloc (ainfo->memory, sz); | |
| if (!out) | |
| { | |
| bfd_set_error (bfd_error_no_memory); | |
| return false; | |
| } | |
| if (!add_memory_shuffle (ainfo, &ainfo->pdr, &ainfo->pdr_end, out, | |
| sz)) | |
| return false; | |
| for (; in < end; in += insz, out += outsz) | |
| { | |
| PDR pdr; | |
| (*input_swap->swap_pdr_in) (input_bfd, in, &pdr); | |
| (*output_swap->swap_pdr_out) (output_bfd, &pdr, out); | |
| } | |
| /* Swap over the optimization information. */ | |
| outsz = external_opt_size; | |
| insz = input_swap->external_opt_size; | |
| in = ((bfd_byte *) input_debug->external_opt | |
| + fdr.ioptBase * insz); | |
| end = in + fdr.copt * insz; | |
| sz = fdr.copt * outsz; | |
| out = (bfd_byte *) objalloc_alloc (ainfo->memory, sz); | |
| if (!out) | |
| { | |
| bfd_set_error (bfd_error_no_memory); | |
| return false; | |
| } | |
| if (!add_memory_shuffle (ainfo, &ainfo->opt, &ainfo->opt_end, out, | |
| sz)) | |
| return false; | |
| for (; in < end; in += insz, out += outsz) | |
| { | |
| OPTR opt; | |
| (*input_swap->swap_opt_in) (input_bfd, in, &opt); | |
| (*output_swap->swap_opt_out) (output_bfd, &opt, out); | |
| } | |
| } | |
| fdr.ipdFirst = output_symhdr->ipdMax; | |
| output_symhdr->ipdMax += fdr.cpd; | |
| fdr.ioptBase = output_symhdr->ioptMax; | |
| output_symhdr->ioptMax += fdr.copt; | |
| if (fdr.crfd <= 0) | |
| { | |
| /* Point this FDR at the table of RFD's we created. */ | |
| fdr.rfdBase = newrfdbase; | |
| fdr.crfd = input_symhdr->ifdMax; | |
| } | |
| else | |
| { | |
| /* Point this FDR at the remapped RFD's. */ | |
| fdr.rfdBase += oldrfdbase; | |
| } | |
| (*swap_fdr_out) (output_bfd, &fdr, fdr_out); | |
| fdr_out += external_fdr_size; | |
| ++output_symhdr->ifdMax; | |
| } | |
| return true; | |
| } | |
| /* Add a string to the debugging information we are accumulating. | |
| Return the offset from the fdr string base. */ | |
| static long | |
| ecoff_add_string (struct accumulate *ainfo, | |
| struct bfd_link_info *info, | |
| struct ecoff_debug_info *debug, | |
| FDR *fdr, | |
| const char *string) | |
| { | |
| HDRR *symhdr; | |
| size_t len; | |
| bfd_size_type ret; | |
| symhdr = &debug->symbolic_header; | |
| len = strlen (string); | |
| if (bfd_link_relocatable (info)) | |
| { | |
| if (!add_memory_shuffle (ainfo, &ainfo->ss, &ainfo->ss_end, | |
| (bfd_byte *) string, len + 1)) | |
| return -1; | |
| ret = symhdr->issMax; | |
| symhdr->issMax += len + 1; | |
| fdr->cbSs += len + 1; | |
| } | |
| else | |
| { | |
| struct string_hash_entry *sh; | |
| sh = string_hash_lookup (&ainfo->str_hash, string, true, true); | |
| if (sh == (struct string_hash_entry *) NULL) | |
| return -1; | |
| if (sh->val == -1) | |
| { | |
| sh->val = symhdr->issMax; | |
| symhdr->issMax += len + 1; | |
| if (ainfo->ss_hash == (struct string_hash_entry *) NULL) | |
| ainfo->ss_hash = sh; | |
| if (ainfo->ss_hash_end | |
| != (struct string_hash_entry *) NULL) | |
| ainfo->ss_hash_end->next = sh; | |
| ainfo->ss_hash_end = sh; | |
| } | |
| ret = sh->val; | |
| } | |
| return ret; | |
| } | |
| /* Add debugging information from a non-ECOFF file. */ | |
| bool | |
| bfd_ecoff_debug_accumulate_other (void * handle, | |
| bfd *output_bfd, | |
| struct ecoff_debug_info *output_debug, | |
| const struct ecoff_debug_swap *output_swap, | |
| bfd *input_bfd, | |
| struct bfd_link_info *info) | |
| { | |
| struct accumulate *ainfo = (struct accumulate *) handle; | |
| void (* const swap_sym_out) (bfd *, const SYMR *, void *) | |
| = output_swap->swap_sym_out; | |
| HDRR *output_symhdr = &output_debug->symbolic_header; | |
| FDR fdr; | |
| asection *sec; | |
| asymbol **symbols; | |
| asymbol **sym_ptr; | |
| asymbol **sym_end; | |
| long symsize; | |
| long symcount; | |
| void * external_fdr; | |
| memset (&fdr, 0, sizeof fdr); | |
| sec = bfd_get_section_by_name (input_bfd, ".text"); | |
| if (sec != NULL) | |
| fdr.adr = sec->output_section->vma + sec->output_offset; | |
| else | |
| { | |
| /* FIXME: What about .init or .fini? */ | |
| fdr.adr = 0; | |
| } | |
| fdr.issBase = output_symhdr->issMax; | |
| fdr.cbSs = 0; | |
| fdr.rss = ecoff_add_string (ainfo, info, output_debug, &fdr, | |
| bfd_get_filename (input_bfd)); | |
| if (fdr.rss == -1) | |
| return false; | |
| fdr.isymBase = output_symhdr->isymMax; | |
| /* Get the local symbols from the input BFD. */ | |
| symsize = bfd_get_symtab_upper_bound (input_bfd); | |
| if (symsize < 0) | |
| return false; | |
| symbols = (asymbol **) bfd_alloc (output_bfd, (bfd_size_type) symsize); | |
| if (symbols == (asymbol **) NULL) | |
| return false; | |
| symcount = bfd_canonicalize_symtab (input_bfd, symbols); | |
| if (symcount < 0) | |
| return false; | |
| sym_end = symbols + symcount; | |
| /* Handle the local symbols. Any external symbols are handled | |
| separately. */ | |
| fdr.csym = 0; | |
| for (sym_ptr = symbols; sym_ptr != sym_end; sym_ptr++) | |
| { | |
| SYMR internal_sym; | |
| void * external_sym; | |
| if (((*sym_ptr)->flags & BSF_EXPORT) != 0) | |
| continue; | |
| memset (&internal_sym, 0, sizeof internal_sym); | |
| internal_sym.iss = ecoff_add_string (ainfo, info, output_debug, &fdr, | |
| (*sym_ptr)->name); | |
| if (internal_sym.iss == -1) | |
| return false; | |
| if (bfd_is_com_section ((*sym_ptr)->section) | |
| || bfd_is_und_section ((*sym_ptr)->section)) | |
| internal_sym.value = (*sym_ptr)->value; | |
| else | |
| internal_sym.value = ((*sym_ptr)->value | |
| + (*sym_ptr)->section->output_offset | |
| + (*sym_ptr)->section->output_section->vma); | |
| internal_sym.st = stNil; | |
| internal_sym.sc = scUndefined; | |
| internal_sym.index = indexNil; | |
| external_sym = objalloc_alloc (ainfo->memory, | |
| output_swap->external_sym_size); | |
| if (!external_sym) | |
| { | |
| bfd_set_error (bfd_error_no_memory); | |
| return false; | |
| } | |
| (*swap_sym_out) (output_bfd, &internal_sym, external_sym); | |
| add_memory_shuffle (ainfo, &ainfo->sym, &ainfo->sym_end, | |
| (bfd_byte *) external_sym, | |
| (unsigned long) output_swap->external_sym_size); | |
| ++fdr.csym; | |
| ++output_symhdr->isymMax; | |
| } | |
| bfd_release (output_bfd, symbols); | |
| /* Leave everything else in the FDR zeroed out. This will cause | |
| the lang field to be langC. The fBigendian field will | |
| indicate little endian format, but it doesn't matter because | |
| it only applies to aux fields and there are none. */ | |
| external_fdr = objalloc_alloc (ainfo->memory, | |
| output_swap->external_fdr_size); | |
| if (!external_fdr) | |
| { | |
| bfd_set_error (bfd_error_no_memory); | |
| return false; | |
| } | |
| (*output_swap->swap_fdr_out) (output_bfd, &fdr, external_fdr); | |
| add_memory_shuffle (ainfo, &ainfo->fdr, &ainfo->fdr_end, | |
| (bfd_byte *) external_fdr, | |
| (unsigned long) output_swap->external_fdr_size); | |
| ++output_symhdr->ifdMax; | |
| return true; | |
| } | |
| /* Set up ECOFF debugging information for the external symbols. | |
| FIXME: This is done using a memory buffer, but it should be | |
| probably be changed to use a shuffle structure. The assembler uses | |
| this interface, so that must be changed to do something else. */ | |
| bool | |
| bfd_ecoff_debug_externals (bfd *abfd, | |
| struct ecoff_debug_info *debug, | |
| const struct ecoff_debug_swap *swap, | |
| bool relocatable, | |
| bool (*get_extr) (asymbol *, EXTR *), | |
| void (*set_index) (asymbol *, bfd_size_type)) | |
| { | |
| HDRR * const symhdr = &debug->symbolic_header; | |
| asymbol **sym_ptr_ptr; | |
| size_t c; | |
| sym_ptr_ptr = bfd_get_outsymbols (abfd); | |
| if (sym_ptr_ptr == NULL) | |
| return true; | |
| for (c = bfd_get_symcount (abfd); c > 0; c--, sym_ptr_ptr++) | |
| { | |
| asymbol *sym_ptr; | |
| EXTR esym; | |
| sym_ptr = *sym_ptr_ptr; | |
| /* Get the external symbol information. */ | |
| if (! (*get_extr) (sym_ptr, &esym)) | |
| continue; | |
| /* If we're producing an executable, move common symbols into | |
| bss. */ | |
| if (! relocatable) | |
| { | |
| if (esym.asym.sc == scCommon) | |
| esym.asym.sc = scBss; | |
| else if (esym.asym.sc == scSCommon) | |
| esym.asym.sc = scSBss; | |
| } | |
| if (bfd_is_com_section (sym_ptr->section) | |
| || bfd_is_und_section (sym_ptr->section) | |
| || sym_ptr->section->output_section == (asection *) NULL) | |
| { | |
| /* FIXME: gas does not keep the value of a small undefined | |
| symbol in the symbol itself, because of relocation | |
| problems. */ | |
| if (esym.asym.sc != scSUndefined | |
| || esym.asym.value == 0 | |
| || sym_ptr->value != 0) | |
| esym.asym.value = sym_ptr->value; | |
| } | |
| else | |
| esym.asym.value = (sym_ptr->value | |
| + sym_ptr->section->output_offset | |
| + sym_ptr->section->output_section->vma); | |
| if (set_index) | |
| (*set_index) (sym_ptr, (bfd_size_type) symhdr->iextMax); | |
| if (! bfd_ecoff_debug_one_external (abfd, debug, swap, | |
| sym_ptr->name, &esym)) | |
| return false; | |
| } | |
| return true; | |
| } | |
| /* Add a single external symbol to the debugging information. */ | |
| bool | |
| bfd_ecoff_debug_one_external (bfd *abfd, | |
| struct ecoff_debug_info *debug, | |
| const struct ecoff_debug_swap *swap, | |
| const char *name, | |
| EXTR *esym) | |
| { | |
| const bfd_size_type external_ext_size = swap->external_ext_size; | |
| void (* const swap_ext_out) (bfd *, const EXTR *, void *) | |
| = swap->swap_ext_out; | |
| HDRR * const symhdr = &debug->symbolic_header; | |
| size_t namelen; | |
| namelen = strlen (name); | |
| if ((size_t) (debug->ssext_end - debug->ssext) | |
| < symhdr->issExtMax + namelen + 1) | |
| { | |
| if (! ecoff_add_bytes ((char **) &debug->ssext, | |
| (char **) &debug->ssext_end, | |
| symhdr->issExtMax + namelen + 1)) | |
| return false; | |
| } | |
| if ((size_t) ((char *) debug->external_ext_end | |
| - (char *) debug->external_ext) | |
| < (symhdr->iextMax + 1) * external_ext_size) | |
| { | |
| char *external_ext = (char *) debug->external_ext; | |
| char *external_ext_end = (char *) debug->external_ext_end; | |
| if (! ecoff_add_bytes ((char **) &external_ext, | |
| (char **) &external_ext_end, | |
| (symhdr->iextMax + 1) * (size_t) external_ext_size)) | |
| return false; | |
| debug->external_ext = external_ext; | |
| debug->external_ext_end = external_ext_end; | |
| } | |
| esym->asym.iss = symhdr->issExtMax; | |
| (*swap_ext_out) (abfd, esym, | |
| ((char *) debug->external_ext | |
| + symhdr->iextMax * swap->external_ext_size)); | |
| ++symhdr->iextMax; | |
| strcpy (debug->ssext + symhdr->issExtMax, name); | |
| symhdr->issExtMax += namelen + 1; | |
| return true; | |
| } | |
| /* Align the ECOFF debugging information. */ | |
| static void | |
| ecoff_align_debug (bfd *abfd ATTRIBUTE_UNUSED, | |
| struct ecoff_debug_info *debug, | |
| const struct ecoff_debug_swap *swap) | |
| { | |
| HDRR * const symhdr = &debug->symbolic_header; | |
| bfd_size_type debug_align, aux_align, rfd_align; | |
| size_t add; | |
| /* Adjust the counts so that structures are aligned. */ | |
| debug_align = swap->debug_align; | |
| aux_align = debug_align / sizeof (union aux_ext); | |
| rfd_align = debug_align / swap->external_rfd_size; | |
| add = debug_align - (symhdr->cbLine & (debug_align - 1)); | |
| if (add != debug_align) | |
| { | |
| if (debug->line != (unsigned char *) NULL) | |
| memset ((debug->line + symhdr->cbLine), 0, add); | |
| symhdr->cbLine += add; | |
| } | |
| add = debug_align - (symhdr->issMax & (debug_align - 1)); | |
| if (add != debug_align) | |
| { | |
| if (debug->ss != (char *) NULL) | |
| memset ((debug->ss + symhdr->issMax), 0, add); | |
| symhdr->issMax += add; | |
| } | |
| add = debug_align - (symhdr->issExtMax & (debug_align - 1)); | |
| if (add != debug_align) | |
| { | |
| if (debug->ssext != (char *) NULL) | |
| memset ((debug->ssext + symhdr->issExtMax), 0, add); | |
| symhdr->issExtMax += add; | |
| } | |
| add = aux_align - (symhdr->iauxMax & (aux_align - 1)); | |
| if (add != aux_align) | |
| { | |
| if (debug->external_aux != (union aux_ext *) NULL) | |
| memset ((debug->external_aux + symhdr->iauxMax), 0, | |
| add * sizeof (union aux_ext)); | |
| symhdr->iauxMax += add; | |
| } | |
| add = rfd_align - (symhdr->crfd & (rfd_align - 1)); | |
| if (add != rfd_align) | |
| { | |
| if (debug->external_rfd != NULL) | |
| memset (((char *) debug->external_rfd | |
| + symhdr->crfd * swap->external_rfd_size), | |
| 0, (size_t) (add * swap->external_rfd_size)); | |
| symhdr->crfd += add; | |
| } | |
| } | |
| /* Return the size required by the ECOFF debugging information. */ | |
| bfd_size_type | |
| bfd_ecoff_debug_size (bfd *abfd, | |
| struct ecoff_debug_info *debug, | |
| const struct ecoff_debug_swap *swap) | |
| { | |
| bfd_size_type tot; | |
| ecoff_align_debug (abfd, debug, swap); | |
| tot = swap->external_hdr_size; | |
| #define ADD(count, size) \ | |
| tot += debug->symbolic_header.count * size | |
| ADD (cbLine, sizeof (unsigned char)); | |
| ADD (idnMax, swap->external_dnr_size); | |
| ADD (ipdMax, swap->external_pdr_size); | |
| ADD (isymMax, swap->external_sym_size); | |
| ADD (ioptMax, swap->external_opt_size); | |
| ADD (iauxMax, sizeof (union aux_ext)); | |
| ADD (issMax, sizeof (char)); | |
| ADD (issExtMax, sizeof (char)); | |
| ADD (ifdMax, swap->external_fdr_size); | |
| ADD (crfd, swap->external_rfd_size); | |
| ADD (iextMax, swap->external_ext_size); | |
| #undef ADD | |
| return tot; | |
| } | |
| /* Write out the ECOFF symbolic header, given the file position it is | |
| going to be placed at. This assumes that the counts are set | |
| correctly. */ | |
| static bool | |
| ecoff_write_symhdr (bfd *abfd, | |
| struct ecoff_debug_info *debug, | |
| const struct ecoff_debug_swap *swap, | |
| file_ptr where) | |
| { | |
| HDRR * const symhdr = &debug->symbolic_header; | |
| char *buff = NULL; | |
| ecoff_align_debug (abfd, debug, swap); | |
| /* Go to the right location in the file. */ | |
| if (bfd_seek (abfd, where, SEEK_SET) != 0) | |
| return false; | |
| where += swap->external_hdr_size; | |
| symhdr->magic = swap->sym_magic; | |
| /* Fill in the file offsets. */ | |
| #define SET(offset, count, size) \ | |
| if (symhdr->count == 0) \ | |
| symhdr->offset = 0; \ | |
| else \ | |
| { \ | |
| symhdr->offset = where; \ | |
| where += symhdr->count * size; \ | |
| } | |
| SET (cbLineOffset, cbLine, sizeof (unsigned char)); | |
| SET (cbDnOffset, idnMax, swap->external_dnr_size); | |
| SET (cbPdOffset, ipdMax, swap->external_pdr_size); | |
| SET (cbSymOffset, isymMax, swap->external_sym_size); | |
| SET (cbOptOffset, ioptMax, swap->external_opt_size); | |
| SET (cbAuxOffset, iauxMax, sizeof (union aux_ext)); | |
| SET (cbSsOffset, issMax, sizeof (char)); | |
| SET (cbSsExtOffset, issExtMax, sizeof (char)); | |
| SET (cbFdOffset, ifdMax, swap->external_fdr_size); | |
| SET (cbRfdOffset, crfd, swap->external_rfd_size); | |
| SET (cbExtOffset, iextMax, swap->external_ext_size); | |
| #undef SET | |
| buff = (char *) bfd_malloc (swap->external_hdr_size); | |
| if (buff == NULL && swap->external_hdr_size != 0) | |
| goto error_return; | |
| (*swap->swap_hdr_out) (abfd, symhdr, buff); | |
| if (bfd_write (buff, swap->external_hdr_size, abfd) | |
| != swap->external_hdr_size) | |
| goto error_return; | |
| free (buff); | |
| return true; | |
| error_return: | |
| free (buff); | |
| return false; | |
| } | |
| /* Write out the ECOFF debugging information. This function assumes | |
| that the information (the pointers and counts) in *DEBUG have been | |
| set correctly. WHERE is the position in the file to write the | |
| information to. This function fills in the file offsets in the | |
| symbolic header. */ | |
| bool | |
| bfd_ecoff_write_debug (bfd *abfd, | |
| struct ecoff_debug_info *debug, | |
| const struct ecoff_debug_swap *swap, | |
| file_ptr where) | |
| { | |
| HDRR * const symhdr = &debug->symbolic_header; | |
| if (! ecoff_write_symhdr (abfd, debug, swap, where)) | |
| return false; | |
| #define WRITE(ptr, count, size, offset) \ | |
| BFD_ASSERT (symhdr->offset == 0 \ | |
| || (bfd_vma) bfd_tell (abfd) == symhdr->offset); \ | |
| if (symhdr->count != 0 \ | |
| && bfd_write (debug->ptr, size * symhdr->count, \ | |
| abfd) != size * symhdr->count) \ | |
| return false; | |
| WRITE (line, cbLine, sizeof (unsigned char), cbLineOffset); | |
| WRITE (external_dnr, idnMax, swap->external_dnr_size, cbDnOffset); | |
| WRITE (external_pdr, ipdMax, swap->external_pdr_size, cbPdOffset); | |
| WRITE (external_sym, isymMax, swap->external_sym_size, cbSymOffset); | |
| WRITE (external_opt, ioptMax, swap->external_opt_size, cbOptOffset); | |
| WRITE (external_aux, iauxMax, sizeof (union aux_ext), cbAuxOffset); | |
| WRITE (ss, issMax, sizeof (char), cbSsOffset); | |
| WRITE (ssext, issExtMax, sizeof (char), cbSsExtOffset); | |
| WRITE (external_fdr, ifdMax, swap->external_fdr_size, cbFdOffset); | |
| WRITE (external_rfd, crfd, swap->external_rfd_size, cbRfdOffset); | |
| WRITE (external_ext, iextMax, swap->external_ext_size, cbExtOffset); | |
| #undef WRITE | |
| return true; | |
| } | |
| /* Write out a shuffle list. */ | |
| static bool | |
| ecoff_write_shuffle (bfd *abfd, | |
| const struct ecoff_debug_swap *swap, | |
| struct shuffle *shuffle, | |
| void * space) | |
| { | |
| struct shuffle *l; | |
| size_t total; | |
| total = 0; | |
| for (l = shuffle; l != NULL; l = l->next) | |
| { | |
| if (! l->filep) | |
| { | |
| if (bfd_write (l->u.memory, l->size, abfd) != l->size) | |
| return false; | |
| } | |
| else | |
| { | |
| if (bfd_seek (l->u.file.input_bfd, l->u.file.offset, SEEK_SET) != 0 | |
| || bfd_read (space, l->size, l->u.file.input_bfd) != l->size | |
| || bfd_write (space, l->size, abfd) != l->size) | |
| return false; | |
| } | |
| total += l->size; | |
| } | |
| if ((total & (swap->debug_align - 1)) != 0) | |
| { | |
| size_t i; | |
| bfd_byte *s; | |
| i = swap->debug_align - (total & (swap->debug_align - 1)); | |
| s = bfd_zmalloc (i); | |
| if (s == NULL && i != 0) | |
| return false; | |
| if (bfd_write (s, i, abfd) != i) | |
| { | |
| free (s); | |
| return false; | |
| } | |
| free (s); | |
| } | |
| return true; | |
| } | |
| /* Write out debugging information using accumulated linker | |
| information. */ | |
| bool | |
| bfd_ecoff_write_accumulated_debug (void * handle, | |
| bfd *abfd, | |
| struct ecoff_debug_info *debug, | |
| const struct ecoff_debug_swap *swap, | |
| struct bfd_link_info *info, | |
| file_ptr where) | |
| { | |
| struct accumulate *ainfo = (struct accumulate *) handle; | |
| void * space = NULL; | |
| bfd_size_type amt; | |
| if (! ecoff_write_symhdr (abfd, debug, swap, where)) | |
| goto error_return; | |
| amt = ainfo->largest_file_shuffle; | |
| space = bfd_malloc (amt); | |
| if (space == NULL && ainfo->largest_file_shuffle != 0) | |
| goto error_return; | |
| if (! ecoff_write_shuffle (abfd, swap, ainfo->line, space) | |
| || ! ecoff_write_shuffle (abfd, swap, ainfo->pdr, space) | |
| || ! ecoff_write_shuffle (abfd, swap, ainfo->sym, space) | |
| || ! ecoff_write_shuffle (abfd, swap, ainfo->opt, space) | |
| || ! ecoff_write_shuffle (abfd, swap, ainfo->aux, space)) | |
| goto error_return; | |
| /* The string table is written out from the hash table if this is a | |
| final link. */ | |
| if (bfd_link_relocatable (info)) | |
| { | |
| BFD_ASSERT (ainfo->ss_hash == (struct string_hash_entry *) NULL); | |
| if (! ecoff_write_shuffle (abfd, swap, ainfo->ss, space)) | |
| goto error_return; | |
| } | |
| else | |
| { | |
| unsigned long total; | |
| bfd_byte null; | |
| struct string_hash_entry *sh; | |
| BFD_ASSERT (ainfo->ss == NULL); | |
| null = 0; | |
| if (bfd_write (&null, 1, abfd) != 1) | |
| goto error_return; | |
| total = 1; | |
| BFD_ASSERT (ainfo->ss_hash == NULL || ainfo->ss_hash->val == 1); | |
| for (sh = ainfo->ss_hash; sh != NULL; sh = sh->next) | |
| { | |
| size_t len; | |
| len = strlen (sh->root.string); | |
| amt = len + 1; | |
| if (bfd_write (sh->root.string, amt, abfd) != amt) | |
| goto error_return; | |
| total += len + 1; | |
| } | |
| if ((total & (swap->debug_align - 1)) != 0) | |
| { | |
| size_t i; | |
| bfd_byte *s; | |
| i = swap->debug_align - (total & (swap->debug_align - 1)); | |
| s = bfd_zmalloc (i); | |
| if (s == NULL && i != 0) | |
| goto error_return; | |
| if (bfd_write (s, i, abfd) != i) | |
| { | |
| free (s); | |
| goto error_return; | |
| } | |
| free (s); | |
| } | |
| } | |
| /* The external strings and symbol are not converted over to using | |
| shuffles. FIXME: They probably should be. */ | |
| amt = debug->symbolic_header.issExtMax; | |
| if (amt != 0 && bfd_write (debug->ssext, amt, abfd) != amt) | |
| goto error_return; | |
| if ((debug->symbolic_header.issExtMax & (swap->debug_align - 1)) != 0) | |
| { | |
| size_t i; | |
| bfd_byte *s; | |
| i = (swap->debug_align | |
| - (debug->symbolic_header.issExtMax & (swap->debug_align - 1))); | |
| s = bfd_zmalloc (i); | |
| if (s == NULL && i != 0) | |
| goto error_return; | |
| if (bfd_write (s, i, abfd) != i) | |
| { | |
| free (s); | |
| goto error_return; | |
| } | |
| free (s); | |
| } | |
| if (! ecoff_write_shuffle (abfd, swap, ainfo->fdr, space) | |
| || ! ecoff_write_shuffle (abfd, swap, ainfo->rfd, space)) | |
| goto error_return; | |
| BFD_ASSERT (debug->symbolic_header.cbExtOffset == 0 | |
| || (debug->symbolic_header.cbExtOffset | |
| == (bfd_vma) bfd_tell (abfd))); | |
| amt = debug->symbolic_header.iextMax * swap->external_ext_size; | |
| if (amt != 0 && bfd_write (debug->external_ext, amt, abfd) != amt) | |
| goto error_return; | |
| free (space); | |
| return true; | |
| error_return: | |
| free (space); | |
| return false; | |
| } | |
| /* Handle the find_nearest_line function for both ECOFF and MIPS ELF | |
| files. */ | |
| /* Free ECOFF debugging info used by find_nearest_line. */ | |
| void | |
| _bfd_ecoff_free_ecoff_debug_info (struct ecoff_debug_info *debug) | |
| { | |
| if (!debug->alloc_syments) | |
| { | |
| free (debug->line); | |
| free (debug->external_dnr); | |
| free (debug->external_pdr); | |
| free (debug->external_sym); | |
| free (debug->external_opt); | |
| free (debug->external_aux); | |
| free (debug->ss); | |
| free (debug->ssext); | |
| free (debug->external_fdr); | |
| free (debug->external_rfd); | |
| free (debug->external_ext); | |
| } | |
| debug->line= NULL; | |
| debug->external_dnr= NULL; | |
| debug->external_pdr= NULL; | |
| debug->external_sym= NULL; | |
| debug->external_opt= NULL; | |
| debug->external_aux= NULL; | |
| debug->ss= NULL; | |
| debug->ssext= NULL; | |
| debug->external_fdr= NULL; | |
| debug->external_rfd= NULL; | |
| debug->external_ext= NULL; | |
| } | |
| /* Compare FDR entries. This is called via qsort. */ | |
| static int | |
| cmp_fdrtab_entry (const void * leftp, const void * rightp) | |
| { | |
| const struct ecoff_fdrtab_entry *lp = | |
| (const struct ecoff_fdrtab_entry *) leftp; | |
| const struct ecoff_fdrtab_entry *rp = | |
| (const struct ecoff_fdrtab_entry *) rightp; | |
| if (lp->base_addr < rp->base_addr) | |
| return -1; | |
| if (lp->base_addr > rp->base_addr) | |
| return 1; | |
| return 0; | |
| } | |
| /* Each file descriptor (FDR) has a memory address, to simplify | |
| looking up an FDR by address, we build a table covering all FDRs | |
| that have a least one procedure descriptor in them. The final | |
| table will be sorted by address so we can look it up via binary | |
| search. */ | |
| static bool | |
| mk_fdrtab (bfd *abfd, | |
| struct ecoff_debug_info * const debug_info, | |
| const struct ecoff_debug_swap * const debug_swap, | |
| struct ecoff_find_line *line_info) | |
| { | |
| struct ecoff_fdrtab_entry *tab; | |
| FDR *fdr_ptr; | |
| FDR *fdr_start; | |
| FDR *fdr_end; | |
| bool stabs; | |
| size_t len; | |
| size_t amt; | |
| fdr_start = debug_info->fdr; | |
| fdr_end = fdr_start + debug_info->symbolic_header.ifdMax; | |
| /* First, let's see how long the table needs to be. */ | |
| for (len = 0, fdr_ptr = fdr_start; fdr_ptr < fdr_end; fdr_ptr++) | |
| { | |
| /* Sanity check fdr procedure descriptor pointer. */ | |
| long ipdMax = debug_info->symbolic_header.ipdMax; | |
| if (fdr_ptr->ipdFirst >= ipdMax | |
| || fdr_ptr->cpd < 0 | |
| || fdr_ptr->cpd > ipdMax - fdr_ptr->ipdFirst) | |
| fdr_ptr->cpd = 0; | |
| /* Skip FDRs that have no PDRs. */ | |
| if (fdr_ptr->cpd == 0) | |
| continue; | |
| ++len; | |
| } | |
| /* Now, create and fill in the table. */ | |
| if (_bfd_mul_overflow (len, sizeof (struct ecoff_fdrtab_entry), &amt)) | |
| { | |
| bfd_set_error (bfd_error_file_too_big); | |
| return false; | |
| } | |
| line_info->fdrtab = (struct ecoff_fdrtab_entry*) bfd_zalloc (abfd, amt); | |
| if (line_info->fdrtab == NULL) | |
| return false; | |
| tab = line_info->fdrtab; | |
| for (fdr_ptr = fdr_start; fdr_ptr < fdr_end; fdr_ptr++) | |
| { | |
| if (fdr_ptr->cpd == 0) | |
| continue; | |
| /* Check whether this file has stabs debugging information. In | |
| a file with stabs debugging information, the second local | |
| symbol is named @stabs. */ | |
| stabs = false; | |
| if (fdr_ptr->csym >= 2) | |
| { | |
| char *sym_ptr; | |
| SYMR sym; | |
| if ((long) ((unsigned long) fdr_ptr->isymBase + 1) <= 0 | |
| || fdr_ptr->isymBase + 1 >= debug_info->symbolic_header.isymMax) | |
| continue; | |
| sym_ptr = ((char *) debug_info->external_sym | |
| + (fdr_ptr->isymBase + 1) * debug_swap->external_sym_size); | |
| (*debug_swap->swap_sym_in) (abfd, sym_ptr, &sym); | |
| if (fdr_ptr->issBase >= 0 | |
| && fdr_ptr->issBase < debug_info->symbolic_header.issMax | |
| && sym.iss >= 0 | |
| && sym.iss < (debug_info->symbolic_header.issMax | |
| - fdr_ptr->issBase) | |
| && strcmp (debug_info->ss + fdr_ptr->issBase + sym.iss, | |
| STABS_SYMBOL) == 0) | |
| stabs = true; | |
| } | |
| if (!stabs) | |
| { | |
| /* eraxxon: There are at least two problems with this computation: | |
| 1) PDRs do *not* contain offsets but full vma's; and typically the | |
| address of the first PDR is the address of the FDR, which will | |
| make (most) of the results of the original computation 0! | |
| 2) Once in a wacky while, the Compaq compiler generated PDR | |
| addresses do not equal the FDR vma, but they (the PDR address) | |
| are still vma's and not offsets. Cf. comments in | |
| 'lookup_line'. */ | |
| /* The address of the first PDR is the offset of that | |
| procedure relative to the beginning of file FDR. */ | |
| tab->base_addr = fdr_ptr->adr; | |
| } | |
| else | |
| { | |
| /* XXX I don't know about stabs, so this is a guess | |
| (davidm@cs.arizona.edu). */ | |
| tab->base_addr = fdr_ptr->adr; | |
| } | |
| tab->fdr = fdr_ptr; | |
| ++tab; | |
| } | |
| len = tab - line_info->fdrtab; | |
| line_info->fdrtab_len = len; | |
| /* Finally, the table is sorted in increasing memory-address order. | |
| The table is mostly sorted already, but there are cases (e.g., | |
| static functions in include files), where this does not hold. | |
| Use "odump -PFv" to verify... */ | |
| qsort (line_info->fdrtab, len, | |
| sizeof (struct ecoff_fdrtab_entry), cmp_fdrtab_entry); | |
| return true; | |
| } | |
| /* Return index of first FDR that covers to OFFSET. */ | |
| static long | |
| fdrtab_lookup (struct ecoff_find_line *line_info, bfd_vma offset) | |
| { | |
| long low, high, len; | |
| long mid = -1; | |
| struct ecoff_fdrtab_entry *tab; | |
| len = line_info->fdrtab_len; | |
| if (len == 0) | |
| return -1; | |
| tab = line_info->fdrtab; | |
| for (low = 0, high = len - 1 ; low != high ;) | |
| { | |
| mid = (high + low) / 2; | |
| if (offset >= tab[mid].base_addr && offset < tab[mid + 1].base_addr) | |
| goto find_min; | |
| if (tab[mid].base_addr > offset) | |
| high = mid; | |
| else | |
| low = mid + 1; | |
| } | |
| /* eraxxon: at this point 'offset' is either lower than the lowest entry or | |
| higher than the highest entry. In the former case high = low = mid = 0; | |
| we want to return -1. In the latter case, low = high and mid = low - 1; | |
| we want to return the index of the highest entry. Only in former case | |
| will the following 'catch-all' test be true. */ | |
| ++mid; | |
| /* Last entry is catch-all for all higher addresses. */ | |
| if (offset < tab[mid].base_addr) | |
| return -1; | |
| find_min: | |
| /* eraxxon: There may be multiple FDRs in the table with the | |
| same base_addr; make sure that we are at the first one. */ | |
| while (mid > 0 && tab[mid - 1].base_addr == tab[mid].base_addr) | |
| --mid; | |
| return mid; | |
| } | |
| /* Look up a line given an address, storing the information in | |
| LINE_INFO->cache. */ | |
| static bool | |
| lookup_line (bfd *abfd, | |
| struct ecoff_debug_info * const debug_info, | |
| const struct ecoff_debug_swap * const debug_swap, | |
| struct ecoff_find_line *line_info) | |
| { | |
| struct ecoff_fdrtab_entry *tab; | |
| bfd_vma offset; | |
| bool stabs; | |
| FDR *fdr_ptr; | |
| int i; | |
| /* eraxxon: note that 'offset' is the full vma, not a section offset. */ | |
| offset = line_info->cache.start; | |
| /* Build FDR table (sorted by object file's base-address) if we | |
| don't have it already. */ | |
| if (line_info->fdrtab == NULL | |
| && !mk_fdrtab (abfd, debug_info, debug_swap, line_info)) | |
| return false; | |
| tab = line_info->fdrtab; | |
| /* Find first FDR for address OFFSET. */ | |
| i = fdrtab_lookup (line_info, offset); | |
| if (i < 0) | |
| return false; /* no FDR, no fun... */ | |
| /* eraxxon: 'fdrtab_lookup' doesn't give what we want, at least for Compaq's | |
| C++ compiler 6.2. Consider three FDRs with starting addresses of x, y, | |
| and z, respectively, such that x < y < z. Assume further that | |
| y < 'offset' < z. It is possible at times that the PDR for 'offset' is | |
| associated with FDR x and *not* with FDR y. Erg!! | |
| From a binary dump of my C++ test case 'moo' using Compaq's coffobjanl | |
| (output format has been edited for our purposes): | |
| FDR [2]: (main.C): First instruction: 0x12000207c <x> | |
| PDR [5] for File [2]: LoopTest__Xv <0x1200020a0> (a) | |
| PDR [7] for File [2]: foo__Xv <0x120002168> | |
| FDR [1]: (-1): First instruction: 0x1200020e8 <y> | |
| PDR [3] for File [1]: <0x120001ad0> (b) | |
| FDR [6]: (-1): First instruction: 0x1200026f0 <z> | |
| (a) In the case of PDR5, the vma is such that the first few instructions | |
| of the procedure can be found. But since the size of this procedure is | |
| 160b, the vma will soon cross into the 'address space' of FDR1 and no | |
| debugging info will be found. How repugnant! | |
| (b) It is also possible for a PDR to have a *lower* vma than its associated | |
| FDR; see FDR1 and PDR3. Gross! | |
| Since the FDRs that are causing so much havok (in this case) 1) do not | |
| describe actual files (fdr.rss == -1), and 2) contain only compiler | |
| generated routines, I thought a simple fix would be to exclude them from | |
| the FDR table in 'mk_fdrtab'. But, besides not knowing for certain | |
| whether this would be correct, it creates an additional problem. If we | |
| happen to ask for source file info on a compiler generated (procedure) | |
| symbol -- which is still in the symbol table -- the result can be | |
| information from a real procedure! This is because compiler generated | |
| procedures with vma's higher than the last FDR in the fdr table will be | |
| associated with a PDR from this FDR, specifically the PDR with the | |
| highest vma. This wasn't a problem before, because each procedure had a | |
| PDR. (Yes, this problem could be eliminated if we kept the size of the | |
| last PDR around, but things are already getting ugly). | |
| Probably, a better solution would be to have a sorted PDR table. Each | |
| PDR would have a pointer to its FDR so file information could still be | |
| obtained. A FDR table could still be constructed if necessary -- since | |
| it only contains pointers, not much extra memory would be used -- but | |
| the PDR table would be searched to locate debugging info. | |
| There is still at least one remaining issue. Sometimes a FDR can have a | |
| bogus name, but contain PDRs that should belong to another FDR with a | |
| real name. E.g: | |
| FDR [3]: 0000000120001b50 (/home/.../Array.H~alt~deccxx_5E5A62AD) | |
| PDR [a] for File [3]: 0000000120001b50 | |
| PDR [b] for File [3]: 0000000120001cf0 | |
| PDR [c] for File [3]: 0000000120001dc8 | |
| PDR [d] for File [3]: 0000000120001e40 | |
| PDR [e] for File [3]: 0000000120001eb8 | |
| PDR [f] for File [3]: 0000000120001f4c | |
| FDR [4]: 0000000120001b50 (/home/.../Array.H) | |
| Here, FDR4 has the correct name, but should (seemingly) contain PDRa-f. | |
| The symbol table for PDR4 does contain symbols for PDRa-f, but so does | |
| the symbol table for FDR3. However the former is different; perhaps this | |
| can be detected easily. (I'm not sure at this point.) This problem only | |
| seems to be associated with files with templates. I am assuming the idea | |
| is that there is a 'fake' FDR (with PDRs) for each differently typed set | |
| of templates that must be generated. Currently, FDR4 is completely | |
| excluded from the FDR table in 'mk_fdrtab' because it contains no PDRs. | |
| Since I don't have time to prepare a real fix for this right now, be | |
| prepared for 'A Horrible Hack' to force the inspection of all non-stabs | |
| FDRs. It's coming... */ | |
| fdr_ptr = tab[i].fdr; | |
| /* Check whether this file has stabs debugging information. In a | |
| file with stabs debugging information, the second local symbol is | |
| named @stabs. */ | |
| stabs = false; | |
| if (fdr_ptr->csym >= 2) | |
| { | |
| char *sym_ptr; | |
| SYMR sym; | |
| if ((long) ((unsigned long) fdr_ptr->isymBase + 1) > 0 | |
| && fdr_ptr->isymBase + 1 < debug_info->symbolic_header.isymMax) | |
| { | |
| sym_ptr = ((char *) debug_info->external_sym | |
| + (fdr_ptr->isymBase + 1) * debug_swap->external_sym_size); | |
| (*debug_swap->swap_sym_in) (abfd, sym_ptr, &sym); | |
| if (fdr_ptr->issBase >= 0 | |
| && fdr_ptr->issBase < debug_info->symbolic_header.issMax | |
| && sym.iss >= 0 | |
| && sym.iss < (debug_info->symbolic_header.issMax | |
| - fdr_ptr->issBase) | |
| && strcmp (debug_info->ss + fdr_ptr->issBase + sym.iss, | |
| STABS_SYMBOL) == 0) | |
| stabs = true; | |
| } | |
| } | |
| line_info->cache.filename = NULL; | |
| line_info->cache.functionname = NULL; | |
| line_info->cache.line_num = 0; | |
| if (!stabs) | |
| { | |
| bfd_size_type external_pdr_size; | |
| char *pdr_ptr; | |
| char *best_pdr = NULL; | |
| FDR *best_fdr; | |
| bfd_signed_vma best_dist = -1; | |
| PDR pdr; | |
| unsigned char *line_ptr; | |
| unsigned char *line_end; | |
| int lineno; | |
| /* This file uses ECOFF debugging information. Each FDR has a | |
| list of procedure descriptors (PDR). The address in the FDR | |
| is the absolute address of the first procedure. The address | |
| in the first PDR gives the offset of that procedure relative | |
| to the object file's base-address. The addresses in | |
| subsequent PDRs specify each procedure's address relative to | |
| the object file's base-address. To make things more juicy, | |
| whenever the PROF bit in the PDR is set, the real entry point | |
| of the procedure may be 16 bytes below what would normally be | |
| the procedure's entry point. Instead, DEC came up with a | |
| wicked scheme to create profiled libraries "on the fly": | |
| instead of shipping a regular and a profiled version of each | |
| library, they insert 16 bytes of unused space in front of | |
| each procedure and set the "prof" bit in the PDR to indicate | |
| that there is a gap there (this is done automagically by "as" | |
| when option "-pg" is specified). Thus, normally, you link | |
| against such a library and, except for lots of 16 byte gaps | |
| between functions, things will behave as usual. However, | |
| when invoking "ld" with option "-pg", it will fill those gaps | |
| with code that calls mcount(). It then moves the function's | |
| entry point down by 16 bytes, and out pops a binary that has | |
| all functions profiled. | |
| NOTE: Neither FDRs nor PDRs are strictly sorted in memory | |
| order. For example, when including header-files that | |
| define functions, the FDRs follow behind the including | |
| file, even though their code may have been generated at | |
| a lower address. File coff-alpha.c from libbfd | |
| illustrates this (use "odump -PFv" to look at a file's | |
| FDR/PDR). Similarly, PDRs are sometimes out of order | |
| as well. An example of this is OSF/1 v3.0 libc's | |
| malloc.c. I'm not sure why this happens, but it could | |
| be due to optimizations that reorder a function's | |
| position within an object-file. | |
| Strategy: | |
| On the first call to this function, we build a table of FDRs | |
| that is sorted by the base-address of the object-file the FDR | |
| is referring to. Notice that each object-file may contain | |
| code from multiple source files (e.g., due to code defined in | |
| include files). Thus, for any given base-address, there may | |
| be multiple FDRs (but this case is, fortunately, uncommon). | |
| lookup(addr) guarantees to return the first FDR that applies | |
| to address ADDR. Thus, after invoking lookup(), we have a | |
| list of FDRs that may contain the PDR for ADDR. Next, we | |
| walk through the PDRs of these FDRs and locate the one that | |
| is closest to ADDR (i.e., for which the difference between | |
| ADDR and the PDR's entry point is positive and minimal). | |
| Once, the right FDR and PDR are located, we simply walk | |
| through the line-number table to lookup the line-number that | |
| best matches ADDR. Obviously, things could be sped up by | |
| keeping a sorted list of PDRs instead of a sorted list of | |
| FDRs. However, this would increase space requirements | |
| considerably, which is undesirable. */ | |
| external_pdr_size = debug_swap->external_pdr_size; | |
| /* eraxxon: The Horrible Hack: Because of the problems above, set 'i' | |
| to 0 so we look through all FDRs. | |
| Because FDR's without any symbols are assumed to be non-stabs, | |
| searching through all FDRs may cause the following code to try to | |
| read stabs FDRs as ECOFF ones. However, I don't think this will | |
| harm anything. */ | |
| i = 0; | |
| /* Search FDR list starting at tab[i] for the PDR that best matches | |
| OFFSET. Normally, the FDR list is only one entry long. */ | |
| best_fdr = NULL; | |
| do | |
| { | |
| /* eraxxon: 'dist' and 'min_dist' can be negative now | |
| because we iterate over every FDR rather than just ones | |
| with a base address less than or equal to 'offset'. */ | |
| bfd_signed_vma dist = -1, min_dist = -1; | |
| char *pdr_hold = NULL; | |
| char *pdr_end; | |
| fdr_ptr = tab[i].fdr; | |
| pdr_ptr = ((char *) debug_info->external_pdr | |
| + fdr_ptr->ipdFirst * external_pdr_size); | |
| pdr_end = pdr_ptr + fdr_ptr->cpd * external_pdr_size; | |
| /* Find PDR that is closest to OFFSET. If pdr.prof is set, | |
| the procedure entry-point *may* be 0x10 below pdr.adr. We | |
| simply pretend that pdr.prof *implies* a lower entry-point. | |
| This is safe because it just means that may identify 4 NOPs | |
| in front of the function as belonging to the function. */ | |
| for (; pdr_ptr < pdr_end; pdr_ptr += external_pdr_size) | |
| { | |
| (*debug_swap->swap_pdr_in) (abfd, pdr_ptr, &pdr); | |
| if (offset >= (pdr.adr - 0x10 * pdr.prof)) | |
| { | |
| dist = offset - (pdr.adr - 0x10 * pdr.prof); | |
| /* eraxxon: 'dist' can be negative now. Note that | |
| 'min_dist' can be negative if 'pdr_hold' below is NULL. */ | |
| if (!pdr_hold || (dist >= 0 && dist < min_dist)) | |
| { | |
| min_dist = dist; | |
| pdr_hold = pdr_ptr; | |
| } | |
| } | |
| } | |
| if (!best_pdr || (min_dist >= 0 && min_dist < best_dist)) | |
| { | |
| best_dist = (bfd_vma) min_dist; | |
| best_fdr = fdr_ptr; | |
| best_pdr = pdr_hold; | |
| } | |
| /* Continue looping until base_addr of next entry is different. */ | |
| } | |
| /* eraxxon: We want to iterate over all FDRs. | |
| See previous comment about 'fdrtab_lookup'. */ | |
| while (++i < line_info->fdrtab_len); | |
| if (!best_fdr || !best_pdr) | |
| return false; /* Shouldn't happen... */ | |
| /* Phew, finally we got something that we can hold onto. */ | |
| fdr_ptr = best_fdr; | |
| pdr_ptr = best_pdr; | |
| (*debug_swap->swap_pdr_in) (abfd, pdr_ptr, &pdr); | |
| /* Now we can look for the actual line number. The line numbers | |
| are stored in a very funky format, which I won't try to | |
| describe. The search is bounded by the end of the FDRs line | |
| number entries. */ | |
| line_ptr = line_end = debug_info->line; | |
| if (fdr_ptr->cbLineOffset < debug_info->symbolic_header.cbLine | |
| && fdr_ptr->cbLine <= (debug_info->symbolic_header.cbLine | |
| - fdr_ptr->cbLineOffset) | |
| && pdr.cbLineOffset <= (debug_info->symbolic_header.cbLine | |
| - fdr_ptr->cbLineOffset)) | |
| { | |
| line_end += fdr_ptr->cbLineOffset + fdr_ptr->cbLine; | |
| line_ptr += fdr_ptr->cbLineOffset + pdr.cbLineOffset; | |
| } | |
| /* Make offset relative to procedure entry. */ | |
| offset -= pdr.adr - 0x10 * pdr.prof; | |
| lineno = pdr.lnLow; | |
| while (line_ptr < line_end) | |
| { | |
| int delta; | |
| unsigned int count; | |
| delta = *line_ptr >> 4; | |
| if (delta >= 0x8) | |
| delta -= 0x10; | |
| count = (*line_ptr & 0xf) + 1; | |
| ++line_ptr; | |
| if (delta == -8) | |
| { | |
| delta = (((line_ptr[0]) & 0xff) << 8) + ((line_ptr[1]) & 0xff); | |
| if (delta >= 0x8000) | |
| delta -= 0x10000; | |
| line_ptr += 2; | |
| } | |
| lineno += delta; | |
| if (offset < count * 4) | |
| { | |
| line_info->cache.stop += count * 4 - offset; | |
| break; | |
| } | |
| offset -= count * 4; | |
| } | |
| /* If fdr_ptr->rss is -1, then this file does not have full | |
| symbols, at least according to gdb/mipsread.c. */ | |
| if (fdr_ptr->rss == -1) | |
| { | |
| EXTR proc_ext; | |
| if (pdr.isym >= 0 | |
| && pdr.isym < debug_info->symbolic_header.iextMax) | |
| { | |
| (*debug_swap->swap_ext_in) | |
| (abfd, ((char *) debug_info->external_ext | |
| + pdr.isym * debug_swap->external_ext_size), | |
| &proc_ext); | |
| if (proc_ext.asym.iss >= 0 | |
| && proc_ext.asym.iss < debug_info->symbolic_header.issExtMax) | |
| line_info->cache.functionname = (debug_info->ssext | |
| + proc_ext.asym.iss); | |
| } | |
| } | |
| else if (fdr_ptr->issBase >= 0 | |
| && fdr_ptr->issBase < debug_info->symbolic_header.issMax | |
| && fdr_ptr->rss >= 0 | |
| && fdr_ptr->rss < (debug_info->symbolic_header.issMax | |
| - fdr_ptr->issBase)) | |
| { | |
| SYMR proc_sym; | |
| line_info->cache.filename = (debug_info->ss | |
| + fdr_ptr->issBase | |
| + fdr_ptr->rss); | |
| if (fdr_ptr->isymBase >= 0 | |
| && fdr_ptr->isymBase < debug_info->symbolic_header.isymMax | |
| && pdr.isym >= 0 | |
| && pdr.isym < (debug_info->symbolic_header.isymMax | |
| - fdr_ptr->isymBase)) | |
| { | |
| (*debug_swap->swap_sym_in) | |
| (abfd, ((char *) debug_info->external_sym | |
| + ((fdr_ptr->isymBase + pdr.isym) | |
| * debug_swap->external_sym_size)), | |
| &proc_sym); | |
| if (proc_sym.iss >= 0 | |
| && proc_sym.iss < (debug_info->symbolic_header.issMax | |
| - fdr_ptr->issBase)) | |
| line_info->cache.functionname = (debug_info->ss | |
| + fdr_ptr->issBase | |
| + proc_sym.iss); | |
| } | |
| } | |
| if (lineno == ilineNil) | |
| lineno = 0; | |
| line_info->cache.line_num = lineno; | |
| } | |
| else | |
| { | |
| bfd_size_type external_sym_size; | |
| const char *directory_name; | |
| const char *main_file_name; | |
| const char *current_file_name; | |
| const char *function_name; | |
| const char *line_file_name; | |
| bfd_vma low_func_vma; | |
| bfd_vma low_line_vma; | |
| bool past_line; | |
| bool past_fn; | |
| char *sym_ptr, *sym_ptr_end; | |
| size_t len, funclen; | |
| char *buffer = NULL; | |
| /* This file uses stabs debugging information. When gcc is not | |
| optimizing, it will put the line number information before | |
| the function name stabs entry. When gcc is optimizing, it | |
| will put the stabs entry for all the function first, followed | |
| by the line number information. (This appears to happen | |
| because of the two output files used by the -mgpopt switch, | |
| which is implied by -O). This means that we must keep | |
| looking through the symbols until we find both a line number | |
| and a function name which are beyond the address we want. */ | |
| directory_name = NULL; | |
| main_file_name = NULL; | |
| current_file_name = NULL; | |
| function_name = NULL; | |
| line_file_name = NULL; | |
| low_func_vma = 0; | |
| low_line_vma = 0; | |
| past_line = false; | |
| past_fn = false; | |
| external_sym_size = debug_swap->external_sym_size; | |
| if (fdr_ptr->isymBase >= 0 | |
| && fdr_ptr->isymBase < debug_info->symbolic_header.isymMax | |
| && fdr_ptr->csym >= 2 | |
| && fdr_ptr->csym < (debug_info->symbolic_header.isymMax | |
| - fdr_ptr->isymBase)) | |
| { | |
| sym_ptr = ((char *) debug_info->external_sym | |
| + (fdr_ptr->isymBase + 2) * external_sym_size); | |
| sym_ptr_end = sym_ptr + (fdr_ptr->csym - 2) * external_sym_size; | |
| } | |
| else | |
| { | |
| sym_ptr = NULL; | |
| sym_ptr_end = sym_ptr; | |
| } | |
| for (; | |
| sym_ptr < sym_ptr_end && (! past_line || ! past_fn); | |
| sym_ptr += external_sym_size) | |
| { | |
| SYMR sym; | |
| (*debug_swap->swap_sym_in) (abfd, sym_ptr, &sym); | |
| if (ECOFF_IS_STAB (&sym)) | |
| { | |
| switch (ECOFF_UNMARK_STAB (sym.index)) | |
| { | |
| case N_SO: | |
| if (fdr_ptr->issBase >= 0 | |
| && fdr_ptr->issBase < debug_info->symbolic_header.issMax | |
| && sym.iss >= 0 | |
| && sym.iss < (debug_info->symbolic_header.issMax | |
| - fdr_ptr->issBase)) | |
| main_file_name = current_file_name | |
| = debug_info->ss + fdr_ptr->issBase + sym.iss; | |
| /* Check the next symbol to see if it is also an | |
| N_SO symbol. */ | |
| if (sym_ptr + external_sym_size < sym_ptr_end) | |
| { | |
| SYMR nextsym; | |
| (*debug_swap->swap_sym_in) (abfd, | |
| sym_ptr + external_sym_size, | |
| &nextsym); | |
| if (ECOFF_IS_STAB (&nextsym) | |
| && ECOFF_UNMARK_STAB (nextsym.index) == N_SO) | |
| { | |
| directory_name = current_file_name; | |
| if (fdr_ptr->issBase >= 0 | |
| && fdr_ptr->issBase < debug_info->symbolic_header.issMax | |
| && nextsym.iss >= 0 | |
| && nextsym.iss < (debug_info->symbolic_header.issMax | |
| - fdr_ptr->issBase)) | |
| main_file_name = current_file_name | |
| = debug_info->ss + fdr_ptr->issBase + nextsym.iss; | |
| sym_ptr += external_sym_size; | |
| } | |
| } | |
| break; | |
| case N_SOL: | |
| if (fdr_ptr->issBase >= 0 | |
| && fdr_ptr->issBase < debug_info->symbolic_header.issMax | |
| && sym.iss >= 0 | |
| && sym.iss < (debug_info->symbolic_header.issMax | |
| - fdr_ptr->issBase)) | |
| current_file_name | |
| = debug_info->ss + fdr_ptr->issBase + sym.iss; | |
| break; | |
| case N_FUN: | |
| if (sym.value > offset) | |
| past_fn = true; | |
| else if (sym.value >= low_func_vma) | |
| { | |
| low_func_vma = sym.value; | |
| if (fdr_ptr->issBase >= 0 | |
| && fdr_ptr->issBase < debug_info->symbolic_header.issMax | |
| && sym.iss >= 0 | |
| && sym.iss < (debug_info->symbolic_header.issMax | |
| - fdr_ptr->issBase)) | |
| function_name | |
| = debug_info->ss + fdr_ptr->issBase + sym.iss; | |
| } | |
| break; | |
| } | |
| } | |
| else if (sym.st == stLabel && sym.index != indexNil) | |
| { | |
| if (sym.value > offset) | |
| past_line = true; | |
| else if (sym.value >= low_line_vma) | |
| { | |
| low_line_vma = sym.value; | |
| line_file_name = current_file_name; | |
| line_info->cache.line_num = sym.index; | |
| } | |
| } | |
| } | |
| if (line_info->cache.line_num != 0) | |
| main_file_name = line_file_name; | |
| /* We need to remove the stuff after the colon in the function | |
| name. We also need to put the directory name and the file | |
| name together. */ | |
| if (function_name == NULL) | |
| len = funclen = 0; | |
| else | |
| len = funclen = strlen (function_name) + 1; | |
| if (main_file_name != NULL | |
| && directory_name != NULL | |
| && main_file_name[0] != '/') | |
| len += strlen (directory_name) + strlen (main_file_name) + 1; | |
| if (len != 0) | |
| { | |
| free (line_info->find_buffer); | |
| buffer = (char *) bfd_malloc ((bfd_size_type) len); | |
| line_info->find_buffer = buffer; | |
| if (buffer == NULL) | |
| return false; | |
| } | |
| if (function_name != NULL) | |
| { | |
| char *colon; | |
| strcpy (buffer, function_name); | |
| colon = strchr (buffer, ':'); | |
| if (colon != NULL) | |
| *colon = '\0'; | |
| line_info->cache.functionname = buffer; | |
| } | |
| if (main_file_name != NULL) | |
| { | |
| if (directory_name == NULL || main_file_name[0] == '/') | |
| line_info->cache.filename = main_file_name; | |
| else | |
| { | |
| sprintf (buffer + funclen, "%s%s", directory_name, | |
| main_file_name); | |
| line_info->cache.filename = buffer + funclen; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| /* Do the work of find_nearest_line. */ | |
| bool | |
| _bfd_ecoff_locate_line (bfd *abfd, | |
| asection *section, | |
| bfd_vma offset, | |
| struct ecoff_debug_info * const debug_info, | |
| const struct ecoff_debug_swap * const debug_swap, | |
| struct ecoff_find_line *line_info, | |
| const char **filename_ptr, | |
| const char **functionname_ptr, | |
| unsigned int *retline_ptr) | |
| { | |
| offset += section->vma; | |
| if (line_info->cache.sect == NULL | |
| || line_info->cache.sect != section | |
| || offset < line_info->cache.start | |
| || offset >= line_info->cache.stop) | |
| { | |
| line_info->cache.sect = section; | |
| line_info->cache.start = offset; | |
| line_info->cache.stop = offset; | |
| if (! lookup_line (abfd, debug_info, debug_swap, line_info)) | |
| { | |
| line_info->cache.sect = NULL; | |
| return false; | |
| } | |
| } | |
| *filename_ptr = line_info->cache.filename; | |
| *functionname_ptr = line_info->cache.functionname; | |
| *retline_ptr = line_info->cache.line_num; | |
| return true; | |
| } | |
| /* These routines copy symbolic information into a memory buffer. | |
| FIXME: The whole point of the shuffle code is to avoid storing | |
| everything in memory, since the linker is such a memory hog. This | |
| code makes that effort useless. It is only called by the MIPS ELF | |
| code when generating a shared library, so it is not that big a | |
| deal, but it should be fixed eventually. */ | |
| /* Collect a shuffle into a memory buffer. */ | |
| static bool | |
| ecoff_collect_shuffle (struct shuffle *l, bfd_byte *buff) | |
| { | |
| for (; l != (struct shuffle *) NULL; l = l->next) | |
| { | |
| if (! l->filep) | |
| memcpy (buff, l->u.memory, l->size); | |
| else | |
| { | |
| if (bfd_seek (l->u.file.input_bfd, l->u.file.offset, SEEK_SET) != 0 | |
| || bfd_read (buff, l->size, l->u.file.input_bfd) != l->size) | |
| return false; | |
| } | |
| buff += l->size; | |
| } | |
| return true; | |
| } | |
| /* Copy PDR information into a memory buffer. */ | |
| bool | |
| _bfd_ecoff_get_accumulated_pdr (void * handle, | |
| bfd_byte *buff) | |
| { | |
| struct accumulate *ainfo = (struct accumulate *) handle; | |
| return ecoff_collect_shuffle (ainfo->pdr, buff); | |
| } | |
| /* Copy symbol information into a memory buffer. */ | |
| bool | |
| _bfd_ecoff_get_accumulated_sym (void * handle, bfd_byte *buff) | |
| { | |
| struct accumulate *ainfo = (struct accumulate *) handle; | |
| return ecoff_collect_shuffle (ainfo->sym, buff); | |
| } | |
| /* Copy the string table into a memory buffer. */ | |
| bool | |
| _bfd_ecoff_get_accumulated_ss (void * handle, bfd_byte *buff) | |
| { | |
| struct accumulate *ainfo = (struct accumulate *) handle; | |
| struct string_hash_entry *sh; | |
| /* The string table is written out from the hash table if this is a | |
| final link. */ | |
| BFD_ASSERT (ainfo->ss == (struct shuffle *) NULL); | |
| *buff++ = '\0'; | |
| BFD_ASSERT (ainfo->ss_hash == NULL || ainfo->ss_hash->val == 1); | |
| for (sh = ainfo->ss_hash; | |
| sh != (struct string_hash_entry *) NULL; | |
| sh = sh->next) | |
| { | |
| size_t len; | |
| len = strlen (sh->root.string); | |
| memcpy (buff, sh->root.string, len + 1); | |
| buff += len + 1; | |
| } | |
| return true; | |
| } |