summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlrich Drepper <drepper@redhat.com>2006-12-09 21:10:19 +0000
committerUlrich Drepper <drepper@redhat.com>2006-12-09 21:10:19 +0000
commit7c0e79ffc201e4828a050b35f2c9d96e3001cb48 (patch)
treeac63405743935e3d48c86187151a62769de62855
parent955e1849b4c4274259cd148c2a986661b3852e60 (diff)
downloadelfutils-7c0e79ffc201e4828a050b35f2c9d96e3001cb48.tar.gz
Compare content of both hash tables.
-rw-r--r--src/ChangeLog6
-rw-r--r--src/elflint.c146
2 files changed, 152 insertions, 0 deletions
diff --git a/src/ChangeLog b/src/ChangeLog
index cbee068b..e89f6c0b 100644
--- a/src/ChangeLog
+++ b/src/ChangeLog
@@ -1,3 +1,9 @@
+2006-12-09 Ulrich Drepper <drepper@redhat.com>
+
+ * elflint.c (compare_hash_gnu_hash): New function. Report if the
+ two hash tables have different content (module expected omission
+ of undefined symbols).
+
2006-08-29 Roland McGrath <roland@redhat.com>
* Makefile.am (MAINTAINERCLEANFILES): New variable.
diff --git a/src/elflint.c b/src/elflint.c
index 57dd716a..e5917392 100644
--- a/src/elflint.c
+++ b/src/elflint.c
@@ -2162,6 +2162,141 @@ section [%2d] '%s': hash table has not even room for initial administrative entr
}
+/* Compare content of both hash tables, it must be identical. */
+static void
+compare_hash_gnu_hash (Ebl *ebl, GElf_Ehdr *ehdr, size_t hash_idx,
+ size_t gnu_hash_idx)
+{
+ Elf_Scn *hash_scn = elf_getscn (ebl->elf, hash_idx);
+ Elf_Data *hash_data = elf_getdata (hash_scn, NULL);
+ GElf_Shdr hash_shdr_mem;
+ GElf_Shdr *hash_shdr = gelf_getshdr (hash_scn, &hash_shdr_mem);
+ Elf_Scn *gnu_hash_scn = elf_getscn (ebl->elf, gnu_hash_idx);
+ Elf_Data *gnu_hash_data = elf_getdata (gnu_hash_scn, NULL);
+ GElf_Shdr gnu_hash_shdr_mem;
+ GElf_Shdr *gnu_hash_shdr = gelf_getshdr (gnu_hash_scn, &gnu_hash_shdr_mem);
+
+ if (hash_shdr == NULL || gnu_hash_shdr == NULL
+ || hash_data == NULL || gnu_hash_data == NULL)
+ /* None of these pointers should be NULL since we used the
+ sections already. We are careful nonetheless. */
+ return;
+
+ /* The link must point to the same symbol table. */
+ if (hash_shdr->sh_link != gnu_hash_shdr->sh_link)
+ {
+ ERROR (gettext ("\
+sh_link in hash sections [%2zu] '%s' and [%2zu] '%s' not identical\n"),
+ hash_idx, elf_strptr (ebl->elf, shstrndx, hash_shdr->sh_name),
+ gnu_hash_idx,
+ elf_strptr (ebl->elf, shstrndx, gnu_hash_shdr->sh_name));
+ return;
+ }
+
+ Elf_Scn *sym_scn = elf_getscn (ebl->elf, hash_shdr->sh_link);
+ Elf_Data *sym_data = elf_getdata (sym_scn, NULL);
+ GElf_Shdr sym_shdr_mem;
+ GElf_Shdr *sym_shdr = gelf_getshdr (sym_scn, &sym_shdr_mem);
+
+ if (sym_data == NULL || sym_shdr == NULL)
+ return;
+
+ int nentries = sym_shdr->sh_size / sym_shdr->sh_entsize;
+ char *used = alloca (nentries);
+ memset (used, '\0', nentries);
+
+ /* First go over the GNU_HASH table and mark the entries as used. */
+ const Elf32_Word *gnu_hasharr = (Elf32_Word *) gnu_hash_data->d_buf;
+ Elf32_Word gnu_nbucket = gnu_hasharr[0];
+ const int bitmap_factor = ehdr->e_ident[EI_CLASS] == ELFCLASS32 ? 1 : 2;
+ const Elf32_Word *gnu_bucket = (gnu_hasharr
+ + (4 + gnu_hasharr[2] * bitmap_factor));
+ const Elf32_Word *gnu_chain = gnu_bucket + gnu_hasharr[0] - gnu_hasharr[1];
+
+ for (Elf32_Word cnt = 0; cnt < gnu_nbucket; ++cnt)
+ {
+ Elf32_Word symidx = gnu_bucket[cnt];
+ if (symidx != STN_UNDEF)
+ do
+ used[symidx] |= 1;
+ while ((gnu_chain[symidx++] & 1u) == 0);
+ }
+
+ /* Now go over the old hash table and check that we cover the same
+ entries. */
+ if (hash_shdr->sh_entsize == sizeof (Elf32_Word))
+ {
+ const Elf32_Word *hasharr = (Elf32_Word *) hash_data->d_buf;
+ Elf32_Word nbucket = hasharr[0];
+ const Elf32_Word *bucket = &hasharr[2];
+ const Elf32_Word *chain = &hasharr[2 + nbucket];
+
+ for (Elf32_Word cnt = 0; cnt < nbucket; ++cnt)
+ {
+ Elf32_Word symidx = bucket[cnt];
+ while (symidx != STN_UNDEF)
+ {
+ used[symidx] |= 2;
+ symidx = chain[symidx];
+ }
+ }
+ }
+ else
+ {
+ const Elf64_Xword *hasharr = (Elf64_Xword *) hash_data->d_buf;
+ Elf64_Xword nbucket = hasharr[0];
+ const Elf64_Xword *bucket = &hasharr[2];
+ const Elf64_Xword *chain = &hasharr[2 + nbucket];
+
+ for (Elf64_Xword cnt = 0; cnt < nbucket; ++cnt)
+ {
+ Elf64_Xword symidx = bucket[cnt];
+ while (symidx != STN_UNDEF)
+ {
+ used[symidx] |= 2;
+ symidx = chain[symidx];
+ }
+ }
+ }
+
+ /* Now see which entries are not set in one or both hash tables
+ (unless the symbol is undefined in which case it can be omitted
+ in the new table format). */
+ if ((used[0] & 1) != 0)
+ ERROR (gettext ("section [%2zu] '%s': reference to symbol index 0\n"),
+ gnu_hash_idx,
+ elf_strptr (ebl->elf, shstrndx, gnu_hash_shdr->sh_name));
+ if ((used[0] & 2) != 0)
+ ERROR (gettext ("section [%2zu] '%s': reference to symbol index 0\n"),
+ hash_idx, elf_strptr (ebl->elf, shstrndx, hash_shdr->sh_name));
+
+ for (int cnt = 1; cnt < nentries; ++cnt)
+ if (used[cnt] != 0 && used[cnt] != 3)
+ {
+ if (used[cnt] == 1)
+ ERROR (gettext ("\
+symbol %d referenced in new hash table in [%2zu] '%s' but not in old hash table in [%2zu] '%s'\n"),
+ cnt, gnu_hash_idx,
+ elf_strptr (ebl->elf, shstrndx, gnu_hash_shdr->sh_name),
+ hash_idx,
+ elf_strptr (ebl->elf, shstrndx, hash_shdr->sh_name));
+ else
+ {
+ GElf_Sym sym_mem;
+ GElf_Sym *sym = gelf_getsym (sym_data, cnt, &sym_mem);
+
+ if (sym != NULL && sym->st_shndx != STN_UNDEF)
+ ERROR (gettext ("\
+symbol %d referenced in old hash table in [%2zu] '%s' but not in new hash table in [%2zu] '%s'\n"),
+ cnt, hash_idx,
+ elf_strptr (ebl->elf, shstrndx, hash_shdr->sh_name),
+ gnu_hash_idx,
+ elf_strptr (ebl->elf, shstrndx, gnu_hash_shdr->sh_name));
+ }
+ }
+}
+
+
static void
check_null (Ebl *ebl, GElf_Shdr *shdr, int idx)
{
@@ -3026,6 +3161,9 @@ zeroth section has nonzero link value while ELF header does not signal overflow
bool dot_interp_section = false;
+ size_t hash_idx = 0;
+ size_t gnu_hash_idx = 0;
+
size_t versym_scnndx = 0;
for (size_t cnt = 1; cnt < shnum; ++cnt)
{
@@ -3313,8 +3451,13 @@ section [%2zu] '%s': relocatable files cannot have dynamic symbol tables\n"),
break;
case SHT_HASH:
+ check_hash (shdr->sh_type, ebl, ehdr, shdr, cnt);
+ hash_idx = cnt;
+ break;
+
case SHT_GNU_HASH:
check_hash (shdr->sh_type, ebl, ehdr, shdr, cnt);
+ gnu_hash_idx = cnt;
break;
case SHT_NULL:
@@ -3384,6 +3527,9 @@ no .gnu.versym section present but .gnu.versym_d or .gnu.versym_r section exist\
ERROR (gettext ("\
.gnu.versym section present without .gnu.versym_d or .gnu.versym_r\n"));
+ if (hash_idx != 0 && gnu_hash_idx != 0)
+ compare_hash_gnu_hash (ebl, ehdr, hash_idx, gnu_hash_idx);
+
free (scnref);
}