diff options
Diffstat (limited to 'bfd/elflink.c')
-rw-r--r-- | bfd/elflink.c | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/bfd/elflink.c b/bfd/elflink.c index e605a8d..b992c39 100644 --- a/bfd/elflink.c +++ b/bfd/elflink.c @@ -2564,3 +2564,348 @@ _bfd_elf_tls_setup (bfd *obfd, struct bfd_link_info *info) return tls; } + +/* Return TRUE iff this is a non-common, definition of a non-function symbol. */ +static bfd_boolean +is_global_data_symbol_definition (bfd *abfd ATTRIBUTE_UNUSED, + Elf_Internal_Sym *sym) +{ + /* Local symbols do not count, but target specific ones might. */ + if (ELF_ST_BIND (sym->st_info) != STB_GLOBAL + && ELF_ST_BIND (sym->st_info) < STB_LOOS) + return FALSE; + + /* Function symbols do not count. */ + if (ELF_ST_TYPE (sym->st_info) == STT_FUNC) + return FALSE; + + /* If the section is undefined, then so is the symbol. */ + if (sym->st_shndx == SHN_UNDEF) + return FALSE; + + /* If the symbol is defined in the common section, then + it is a common definition and so does not count. */ + if (sym->st_shndx == SHN_COMMON) + return FALSE; + + /* If the symbol is in a target specific section then we + must rely upon the backend to tell us what it is. */ + if (sym->st_shndx >= SHN_LORESERVE && sym->st_shndx < SHN_ABS) + /* FIXME - this function is not coded yet: + + return _bfd_is_global_symbol_definition (abfd, sym); + + Instead for now assume that the definition is not global, + Even if this is wrong, at least the linker will behave + in the same way that it used to do. */ + return FALSE; + + return TRUE; +} + +/* Search the symbol table of the archive element of the archive ABFD + whose archive map contains a mention of SYMDEF, and determine if + the symbol is defined in this element. */ +static bfd_boolean +elf_link_is_defined_archive_symbol (bfd * abfd, carsym * symdef) +{ + Elf_Internal_Shdr * hdr; + bfd_size_type symcount; + bfd_size_type extsymcount; + bfd_size_type extsymoff; + Elf_Internal_Sym *isymbuf; + Elf_Internal_Sym *isym; + Elf_Internal_Sym *isymend; + bfd_boolean result; + + abfd = _bfd_get_elt_at_filepos (abfd, symdef->file_offset); + if (abfd == NULL) + return FALSE; + + if (! bfd_check_format (abfd, bfd_object)) + return FALSE; + + /* If we have already included the element containing this symbol in the + link then we do not need to include it again. Just claim that any symbol + it contains is not a definition, so that our caller will not decide to + (re)include this element. */ + if (abfd->archive_pass) + return FALSE; + + /* Select the appropriate symbol table. */ + if ((abfd->flags & DYNAMIC) == 0 || elf_dynsymtab (abfd) == 0) + hdr = &elf_tdata (abfd)->symtab_hdr; + else + hdr = &elf_tdata (abfd)->dynsymtab_hdr; + + symcount = hdr->sh_size / get_elf_backend_data (abfd)->s->sizeof_sym; + + /* The sh_info field of the symtab header tells us where the + external symbols start. We don't care about the local symbols. */ + if (elf_bad_symtab (abfd)) + { + extsymcount = symcount; + extsymoff = 0; + } + else + { + extsymcount = symcount - hdr->sh_info; + extsymoff = hdr->sh_info; + } + + if (extsymcount == 0) + return FALSE; + + /* Read in the symbol table. */ + isymbuf = bfd_elf_get_elf_syms (abfd, hdr, extsymcount, extsymoff, + NULL, NULL, NULL); + if (isymbuf == NULL) + return FALSE; + + /* Scan the symbol table looking for SYMDEF. */ + result = FALSE; + for (isym = isymbuf, isymend = isymbuf + extsymcount; isym < isymend; isym++) + { + const char *name; + + name = bfd_elf_string_from_elf_section (abfd, hdr->sh_link, + isym->st_name); + if (name == NULL) + break; + + if (strcmp (name, symdef->name) == 0) + { + result = is_global_data_symbol_definition (abfd, isym); + break; + } + } + + free (isymbuf); + + return result; +} + +/* Add symbols from an ELF archive file to the linker hash table. We + don't use _bfd_generic_link_add_archive_symbols because of a + problem which arises on UnixWare. The UnixWare libc.so is an + archive which includes an entry libc.so.1 which defines a bunch of + symbols. The libc.so archive also includes a number of other + object files, which also define symbols, some of which are the same + as those defined in libc.so.1. Correct linking requires that we + consider each object file in turn, and include it if it defines any + symbols we need. _bfd_generic_link_add_archive_symbols does not do + this; it looks through the list of undefined symbols, and includes + any object file which defines them. When this algorithm is used on + UnixWare, it winds up pulling in libc.so.1 early and defining a + bunch of symbols. This means that some of the other objects in the + archive are not included in the link, which is incorrect since they + precede libc.so.1 in the archive. + + Fortunately, ELF archive handling is simpler than that done by + _bfd_generic_link_add_archive_symbols, which has to allow for a.out + oddities. In ELF, if we find a symbol in the archive map, and the + symbol is currently undefined, we know that we must pull in that + object file. + + Unfortunately, we do have to make multiple passes over the symbol + table until nothing further is resolved. */ + +bfd_boolean +_bfd_elf_link_add_archive_symbols (bfd *abfd, + struct bfd_link_info *info) +{ + symindex c; + bfd_boolean *defined = NULL; + bfd_boolean *included = NULL; + carsym *symdefs; + bfd_boolean loop; + bfd_size_type amt; + + if (! bfd_has_map (abfd)) + { + /* An empty archive is a special case. */ + if (bfd_openr_next_archived_file (abfd, NULL) == NULL) + return TRUE; + bfd_set_error (bfd_error_no_armap); + return FALSE; + } + + /* Keep track of all symbols we know to be already defined, and all + files we know to be already included. This is to speed up the + second and subsequent passes. */ + c = bfd_ardata (abfd)->symdef_count; + if (c == 0) + return TRUE; + amt = c; + amt *= sizeof (bfd_boolean); + defined = bfd_zmalloc (amt); + included = bfd_zmalloc (amt); + if (defined == NULL || included == NULL) + goto error_return; + + symdefs = bfd_ardata (abfd)->symdefs; + + do + { + file_ptr last; + symindex i; + carsym *symdef; + carsym *symdefend; + + loop = FALSE; + last = -1; + + symdef = symdefs; + symdefend = symdef + c; + for (i = 0; symdef < symdefend; symdef++, i++) + { + struct elf_link_hash_entry *h; + bfd *element; + struct bfd_link_hash_entry *undefs_tail; + symindex mark; + + if (defined[i] || included[i]) + continue; + if (symdef->file_offset == last) + { + included[i] = TRUE; + continue; + } + + h = elf_link_hash_lookup (elf_hash_table (info), symdef->name, + FALSE, FALSE, FALSE); + + if (h == NULL) + { + char *p, *copy; + size_t len, first; + + /* If this is a default version (the name contains @@), + look up the symbol again with only one `@' as well + as without the version. The effect is that references + to the symbol with and without the version will be + matched by the default symbol in the archive. */ + + p = strchr (symdef->name, ELF_VER_CHR); + if (p == NULL || p[1] != ELF_VER_CHR) + continue; + + /* First check with only one `@'. */ + len = strlen (symdef->name); + copy = bfd_alloc (abfd, len); + if (copy == NULL) + goto error_return; + first = p - symdef->name + 1; + memcpy (copy, symdef->name, first); + memcpy (copy + first, symdef->name + first + 1, len - first); + + h = elf_link_hash_lookup (elf_hash_table (info), copy, + FALSE, FALSE, FALSE); + + if (h == NULL) + { + /* We also need to check references to the symbol + without the version. */ + + copy[first - 1] = '\0'; + h = elf_link_hash_lookup (elf_hash_table (info), + copy, FALSE, FALSE, FALSE); + } + + bfd_release (abfd, copy); + } + + if (h == NULL) + continue; + + if (h->root.type == bfd_link_hash_common) + { + /* We currently have a common symbol. The archive map contains + a reference to this symbol, so we may want to include it. We + only want to include it however, if this archive element + contains a definition of the symbol, not just another common + declaration of it. + + Unfortunately some archivers (including GNU ar) will put + declarations of common symbols into their archive maps, as + well as real definitions, so we cannot just go by the archive + map alone. Instead we must read in the element's symbol + table and check that to see what kind of symbol definition + this is. */ + if (! elf_link_is_defined_archive_symbol (abfd, symdef)) + continue; + } + else if (h->root.type != bfd_link_hash_undefined) + { + if (h->root.type != bfd_link_hash_undefweak) + defined[i] = TRUE; + continue; + } + + /* We need to include this archive member. */ + element = _bfd_get_elt_at_filepos (abfd, symdef->file_offset); + if (element == NULL) + goto error_return; + + if (! bfd_check_format (element, bfd_object)) + goto error_return; + + /* Doublecheck that we have not included this object + already--it should be impossible, but there may be + something wrong with the archive. */ + if (element->archive_pass != 0) + { + bfd_set_error (bfd_error_bad_value); + goto error_return; + } + element->archive_pass = 1; + + undefs_tail = info->hash->undefs_tail; + + if (! (*info->callbacks->add_archive_element) (info, element, + symdef->name)) + goto error_return; + if (! bfd_link_add_symbols (element, info)) + goto error_return; + + /* If there are any new undefined symbols, we need to make + another pass through the archive in order to see whether + they can be defined. FIXME: This isn't perfect, because + common symbols wind up on undefs_tail and because an + undefined symbol which is defined later on in this pass + does not require another pass. This isn't a bug, but it + does make the code less efficient than it could be. */ + if (undefs_tail != info->hash->undefs_tail) + loop = TRUE; + + /* Look backward to mark all symbols from this object file + which we have already seen in this pass. */ + mark = i; + do + { + included[mark] = TRUE; + if (mark == 0) + break; + --mark; + } + while (symdefs[mark].file_offset == symdef->file_offset); + + /* We mark subsequent symbols from this object file as we go + on through the loop. */ + last = symdef->file_offset; + } + } + while (loop); + + free (defined); + free (included); + + return TRUE; + + error_return: + if (defined != NULL) + free (defined); + if (included != NULL) + free (included); + return FALSE; +} |