Skip to content

Commit b7e64b9

Browse files
authored
Merge pull request #335 from NixOS/replace-lib
Rebase of #237
2 parents a174cf3 + b02a149 commit b7e64b9

File tree

3 files changed

+60
-10
lines changed

3 files changed

+60
-10
lines changed

src/patchelf.cc

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <sstream>
2525
#include <stdexcept>
2626
#include <string>
27+
#include <unordered_map>
2728
#include <vector>
2829

2930
#include <cassert>
@@ -1584,6 +1585,7 @@ void ElfFile<ElfFileParamNames>::replaceNeeded(const std::map<std::string, std::
15841585
unsigned int verNeedNum = 0;
15851586

15861587
unsigned int dynStrAddedBytes = 0;
1588+
std::unordered_map<std::string, Elf_Off> addedStrings;
15871589

15881590
for ( ; rdi(dyn->d_tag) != DT_NULL; dyn++) {
15891591
if (rdi(dyn->d_tag) == DT_NEEDED) {
@@ -1594,15 +1596,25 @@ void ElfFile<ElfFileParamNames>::replaceNeeded(const std::map<std::string, std::
15941596

15951597
debug("replacing DT_NEEDED entry '%s' with '%s'\n", name, replacement.c_str());
15961598

1599+
auto a = addedStrings.find(replacement);
1600+
// the same replacement string has already been added, reuse it
1601+
if (a != addedStrings.end()) {
1602+
wri(dyn->d_un.d_val, a->second);
1603+
continue;
1604+
}
1605+
15971606
// technically, the string referred by d_val could be used otherwise, too (although unlikely)
15981607
// we'll therefore add a new string
15991608
debug("resizing .dynstr ...\n");
16001609

1610+
// relative location of the new string
1611+
Elf_Off strOffset = rdi(shdrDynStr.sh_size) + dynStrAddedBytes;
16011612
std::string & newDynStr = replaceSection(".dynstr",
1602-
rdi(shdrDynStr.sh_size) + replacement.size() + 1 + dynStrAddedBytes);
1603-
setSubstr(newDynStr, rdi(shdrDynStr.sh_size) + dynStrAddedBytes, replacement + '\0');
1613+
strOffset + replacement.size() + 1);
1614+
setSubstr(newDynStr, strOffset, replacement + '\0');
16041615

1605-
wri(dyn->d_un.d_val, rdi(shdrDynStr.sh_size) + dynStrAddedBytes);
1616+
wri(dyn->d_un.d_val, strOffset);
1617+
addedStrings[replacement] = strOffset;
16061618

16071619
dynStrAddedBytes += replacement.size() + 1;
16081620

@@ -1636,6 +1648,13 @@ void ElfFile<ElfFileParamNames>::replaceNeeded(const std::map<std::string, std::
16361648
debug("found .gnu.version_r with %i entries, strings in %s\n", verNeedNum, versionRStringsSName.c_str());
16371649

16381650
unsigned int verStrAddedBytes = 0;
1651+
// It may be that it is .dynstr again, in which case we must take the already
1652+
// added bytes into account.
1653+
if (versionRStringsSName == ".dynstr")
1654+
verStrAddedBytes += dynStrAddedBytes;
1655+
else
1656+
// otherwise the already added strings can't be reused
1657+
addedStrings.clear();
16391658

16401659
auto need = (Elf_Verneed *)(fileContents->data() + rdi(shdrVersionR.sh_offset));
16411660
while (verNeedNum > 0) {
@@ -1645,15 +1664,24 @@ void ElfFile<ElfFileParamNames>::replaceNeeded(const std::map<std::string, std::
16451664
auto replacement = i->second;
16461665

16471666
debug("replacing .gnu.version_r entry '%s' with '%s'\n", file, replacement.c_str());
1648-
debug("resizing string section %s ...\n", versionRStringsSName.c_str());
16491667

1650-
std::string & newVerDynStr = replaceSection(versionRStringsSName,
1651-
rdi(shdrVersionRStrings.sh_size) + replacement.size() + 1 + verStrAddedBytes);
1652-
setSubstr(newVerDynStr, rdi(shdrVersionRStrings.sh_size) + verStrAddedBytes, replacement + '\0');
1668+
auto a = addedStrings.find(replacement);
1669+
// the same replacement string has already been added, reuse it
1670+
if (a != addedStrings.end()) {
1671+
wri(need->vn_file, a->second);
1672+
} else {
1673+
debug("resizing string section %s ...\n", versionRStringsSName.c_str());
1674+
1675+
Elf_Off strOffset = rdi(shdrVersionRStrings.sh_size) + verStrAddedBytes;
1676+
std::string & newVerDynStr = replaceSection(versionRStringsSName,
1677+
strOffset + replacement.size() + 1);
1678+
setSubstr(newVerDynStr, strOffset, replacement + '\0');
16531679

1654-
wri(need->vn_file, rdi(shdrVersionRStrings.sh_size) + verStrAddedBytes);
1680+
wri(need->vn_file, strOffset);
1681+
addedStrings[replacement] = strOffset;
16551682

1656-
verStrAddedBytes += replacement.size() + 1;
1683+
verStrAddedBytes += replacement.size() + 1;
1684+
}
16571685

16581686
changed = true;
16591687
} else {

tests/Makefile.am

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ src_TESTS = \
3838
args-from-file.sh \
3939
basic-flags.sh \
4040
set-empty-rpath.sh \
41-
phdr-corruption.sh
41+
phdr-corruption.sh \
42+
replace-needed.sh
4243

4344
build_TESTS = \
4445
$(no_rpath_arch_TESTS)

tests/replace-needed.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#! /bin/sh -e
2+
SCRATCH=scratch/$(basename $0 .sh)
3+
4+
rm -rf ${SCRATCH}
5+
mkdir -p ${SCRATCH}
6+
7+
oldNeeded=$(../src/patchelf --print-needed big-dynstr)
8+
oldLibc=$(../src/patchelf --print-needed big-dynstr | grep -v 'foo\.so')
9+
../src/patchelf --output ${SCRATCH}/big-needed --replace-needed ${oldLibc} long_long_very_long_libc.so.6 --replace-needed libfoo.so lf.so big-dynstr
10+
11+
if [ -z "$(../src/patchelf --print-needed ${SCRATCH}/big-needed | grep -Fx "long_long_very_long_libc.so.6")" ]; then
12+
echo "library long_long_very_long_libc.so.6 not found as NEEDED"
13+
../src/patchelf --print-needed ${SCRATCH}/big-needed
14+
exit 1
15+
fi
16+
17+
if [ -z "$(../src/patchelf --print-needed ${SCRATCH}/big-needed | grep -Fx "lf.so")" ]; then
18+
echo "library lf.so not found as NEEDED"
19+
../src/patchelf --print-needed ${SCRATCH}/big-needed
20+
exit 1
21+
fi

0 commit comments

Comments
 (0)