diff --git a/include/LIEF/ELF/Builder.hpp b/include/LIEF/ELF/Builder.hpp index f63ab99..2197e68 100644 --- a/include/LIEF/ELF/Builder.hpp +++ b/include/LIEF/ELF/Builder.hpp @@ -93,6 +93,8 @@ class LIEF_API Builder { template<typename ELF_T> void build_symbol_gnuhash(void); + uint32_t sort_dynamic_symbols(void); + void build_empty_symbol_gnuhash(void); template<typename ELF_T> diff --git a/src/ELF/Builder.cpp b/src/ELF/Builder.cpp index d51cea9..5727a81 100644 --- a/src/ELF/Builder.cpp +++ b/src/ELF/Builder.cpp @@ -111,6 +111,46 @@ void Builder::write(const std::string& filename) const { } +uint32_t Builder::sort_dynamic_symbols(void) { + auto it_begin = std::begin(this->binary_->dynamic_symbols_); + auto it_end = std::end(this->binary_->dynamic_symbols_); + + auto it_first_non_local_symbol = + std::stable_partition(it_begin, it_end, [](const Symbol* sym) { + return sym->binding() == SYMBOL_BINDINGS::STB_LOCAL; + }); + uint32_t first_non_local_symbol_index = + std::distance(it_begin, it_first_non_local_symbol); + std::string section_name = ".dynsym"; + if (this->binary_->has_section(section_name)) { + Section& section = this->binary_->get_section(section_name); + if (section.information() != first_non_local_symbol_index) { + // TODO: Erase null entries of dynamic symbol table and symbol version table + // if information of .dynsym section is smaller than null entries num. + LIEF_WARN("information of {} section changes from {:d} to {:d}", + section_name, + section.information(), + first_non_local_symbol_index); + section.information(first_non_local_symbol_index); + } + } + + auto it_first_exported_symbol = std::stable_partition( + it_first_non_local_symbol, it_end, [](const Symbol* sym) { + return sym->shndx() == + static_cast<uint16_t>(SYMBOL_SECTION_INDEX::SHN_UNDEF); + }); + uint32_t first_exported_symbol_index = + std::distance(it_begin, it_first_exported_symbol); + if (this->binary_->gnu_hash().symbol_index() != first_exported_symbol_index) { + LIEF_WARN("symndx of .gnu.hash section changes from {:d} to {:d}", + this->binary_->gnu_hash().symbol_index(), + first_exported_symbol_index); + } + return first_exported_symbol_index; +} + + void Builder::build_empty_symbol_gnuhash(void) { LIEF_DEBUG("Build empty GNU Hash"); auto&& it_gnuhash = std::find_if( diff --git a/src/ELF/Builder.tcc b/src/ELF/Builder.tcc index 657dcb9..4b4077d 100644 --- a/src/ELF/Builder.tcc +++ b/src/ELF/Builder.tcc @@ -892,7 +892,7 @@ void Builder::build_symbol_gnuhash(void) { const GnuHash& gnu_hash = this->binary_->gnu_hash(); const uint32_t nb_buckets = gnu_hash.nb_buckets(); - const uint32_t symndx = gnu_hash.symbol_index(); + const uint32_t symndx = sort_dynamic_symbols(); const uint32_t maskwords = gnu_hash.maskwords(); const uint32_t shift2 = gnu_hash.shift2(); diff --git a/tests/elf/test_dynamic.py b/tests/elf/test_dynamic.py index 40c3b7b..1d1849d 100644 --- a/tests/elf/test_dynamic.py +++ b/tests/elf/test_dynamic.py @@ -126,6 +126,46 @@ class TestDynamic(TestCase): def setUp(self): self.logger = logging.getLogger(__name__) + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_add_dynamic_symbols(self): + sample = LibAddSample() + libadd = lief.parse(sample.libadd) + binadd = lief.parse(sample.binadd) + dynamic_symbols = list(libadd.dynamic_symbols) + for sym in dynamic_symbols: + libadd.add_dynamic_symbol(sym) + dynamic_section = libadd.get_section(".dynsym") + libadd.extend(dynamic_section, dynamic_section.entry_size * (len(dynamic_symbols) * 2)) + libadd.write(sample.libadd) + + p = Popen([sample.binadd_bin, '1', '2'], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env={"LD_LIBRARY_PATH": sample.directory}) + stdout, _ = p.communicate() + if p.returncode > 0: + self.logger.fatal(stdout.decode("utf8")) + self.assertEqual(p.returncode, 0) + self.logger.debug(stdout.decode("utf8")) + self.assertIsNotNone(re.search(r'From myLIb, a \+ b = 3', stdout.decode("utf8"))) + + libadd = lief.parse(sample.libadd) + dynamic_section = libadd.get_section(".dynsym") + # TODO: Size of libadd.dynamic_symbols is larger than dynamic_symbols_size. + dynamic_symbols_size = int(dynamic_section.size / dynamic_section.entry_size) + dynamic_symbols = list(libadd.dynamic_symbols)[:dynamic_symbols_size] + first_not_null_symbol_index = dynamic_section.information + first_exported_symbol_index = next( + i for i, sym in enumerate(dynamic_symbols) if sym.shndx != 0) + self.assertTrue(all(map( + lambda sym: sym.shndx == 0 and sym.binding == lief.ELF.SYMBOL_BINDINGS.LOCAL, + dynamic_symbols[:first_not_null_symbol_index]))) + self.assertTrue(all(map( + lambda sym: sym.shndx == 0 and sym.binding != lief.ELF.SYMBOL_BINDINGS.LOCAL, + dynamic_symbols[first_not_null_symbol_index:first_exported_symbol_index]))) + self.assertTrue(all(map( + lambda sym: sym.shndx != 0, + dynamic_symbols[first_exported_symbol_index:]))) @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") def test_remove_library(self):