From 618220bfb55c875d6a4d197cb24fe632ac93ec85 Mon Sep 17 00:00:00 2001 From: Wolfgang Grandegger Date: Mon, 20 Feb 2017 16:29:24 +0100 Subject: [PATCH] Add option to make the rpath relative under a specified root directory Running "patchelf" with the option "--make-rpath-relative ROOTDIR" will modify or delete the RPATHDIRs according the following rules similar to Martin's patches [1] making the Buildroot toolchaing/SDK relocatable. RPATHDIR starts with "$ORIGIN": The original build-system already took care of setting a relative RPATH, resolve it and test if it's valid (does exist) RPATHDIR starts with ROOTDIR: The original build-system added some absolute RPATH (absolute on the build machine). Test if it's valid (does exist). ROOTDIR/RPATHDIR exists: The original build-system already took care of setting an absolute RPATH (absolute in the final rootfs), resolve it and test if it's valid (does exist). RPATHDIR points somewhere else: (can be anywhere: build trees, staging tree, host location, non-existing location, etc.). Just discard such a path. The option "--no-standard-libs" will discard RPATHDIRs ROOTDIR/lib and ROOTDIR/usr/lib. Like "--shrink-rpath", RPATHDIRs are also discarded if the directories do not contain a library referenced by the DT_NEEDED fields. If the option "--relative-to-file" is given, the rpath will start with "$ORIGIN" making it relative to the ELF file, otherwise an absolute path relative to ROOTDIR will be used. A pull request for a similar patch [2] for mainline inclusion is pending. [1] http://lists.busybox.net/pipermail/buildroot/2016-April/159422.html [2] https://github.com/NixOS/patchelf/pull/118 Signed-off-by: Wolfgang Grandegger --- src/patchelf.cc | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 175 insertions(+), 21 deletions(-) diff --git a/src/patchelf.cc b/src/patchelf.cc index 1d9a772..35b4a33 100644 --- a/src/patchelf.cc +++ b/src/patchelf.cc @@ -46,6 +46,10 @@ static bool debugMode = false; static bool forceRPath = false; +static bool noStandardLibDirs = false; + +static bool relativeToFile = false; + static string fileName; static int pageSize = PAGESIZE; @@ -77,6 +81,49 @@ static unsigned int getPageSize(){ return pageSize; } +static bool absolutePathExists(const string & path, string & canonicalPath) +{ + char *cpath = realpath(path.c_str(), NULL); + if (cpath) { + canonicalPath = cpath; + free(cpath); + return true; + } else { + return false; + } +} + +static string makePathRelative(const string & path, + const string & refPath) +{ + string relPath = "$ORIGIN"; + string p = path, refP = refPath; + size_t pos; + + /* Strip the common part of path and refPath */ + while (true) { + pos = p.find_first_of('/', 1); + if (refP.find_first_of('/', 1) != pos) + break; + if (p.substr(0, pos) != refP.substr(0, pos)) + break; + if (pos == string::npos) + break; + p = p.substr(pos); + refP = refP.substr(pos); + } + /* Check if both pathes are equal */ + if (p != refP) { + pos = 0; + while (pos != string::npos) { + pos =refP.find_first_of('/', pos + 1); + relPath.append("/.."); + } + relPath.append(p); + } + + return relPath; +} template class ElfFile @@ -183,9 +230,13 @@ public: void setInterpreter(const string & newInterpreter); - typedef enum { rpPrint, rpShrink, rpSet, rpRemove } RPathOp; + typedef enum { rpPrint, rpShrink, rpMakeRelative, rpSet, rpRemove} RPathOp; + + bool libFoundInRPath(const string & dirName, + const vector neededLibs, + vector & neededLibFound); - void modifyRPath(RPathOp op, string newRPath); + void modifyRPath(RPathOp op, string rootDir, string newRPath); void addNeeded(set libs); @@ -1041,7 +1092,27 @@ static void concatToRPath(string & rpath, const string & path) template -void ElfFile::modifyRPath(RPathOp op, string newRPath) +bool ElfFile::libFoundInRPath(const string & dirName, + const vector neededLibs, vector & neededLibFound) +{ + /* For each library that we haven't found yet, see if it + exists in this directory. */ + bool libFound = false; + for (unsigned int j = 0; j < neededLibs.size(); ++j) + if (!neededLibFound[j]) { + string libName = dirName + "/" + neededLibs[j]; + struct stat st; + if (stat(libName.c_str(), &st) == 0) { + neededLibFound[j] = true; + libFound = true; + } + } + return libFound; +} + + +template +void ElfFile::modifyRPath(RPathOp op, string rootDir, string newRPath) { Elf_Shdr & shdrDynamic = findSection(".dynamic"); @@ -1096,6 +1167,11 @@ void ElfFile::modifyRPath(RPathOp op, string newRPath) return; } + if (op == rpMakeRelative && !rpath) { + debug("no RPATH to make relative\n"); + return; + } + if (op == rpShrink && !rpath) { debug("no RPATH to shrink\n"); return; @@ -1120,26 +1196,86 @@ void ElfFile::modifyRPath(RPathOp op, string newRPath) continue; } - /* For each library that we haven't found yet, see if it - exists in this directory. */ - bool libFound = false; - for (unsigned int j = 0; j < neededLibs.size(); ++j) - if (!neededLibFound[j]) { - string libName = dirName + "/" + neededLibs[j]; - struct stat st; - if (stat(libName.c_str(), &st) == 0) { - neededLibFound[j] = true; - libFound = true; - } - } - - if (!libFound) + if (!libFoundInRPath(dirName, neededLibs, neededLibFound)) debug("removing directory `%s' from RPATH\n", dirName.c_str()); else concatToRPath(newRPath, dirName); } } + /* Make the the RPATH relative to the specified path */ + if (op == rpMakeRelative) { + vector neededLibFound(neededLibs.size(), false); + string fileDir = fileName.substr(0, fileName.find_last_of("/")); + + newRPath = ""; + + vector rpathDirs = splitColonDelimitedString(rpath); + for (vector::iterator it = rpathDirs.begin(); it != rpathDirs.end(); ++it) { + const string & dirName = *it; + + string canonicalPath; + + /* Figure out if we should keep or discard the path. There are several + cases to be handled: + "dirName" starts with "$ORIGIN": + The original build-system already took care of setting a relative + RPATH. Resolve it and test if it's valid (does exist). + "dirName" start with "rootDir": + The original build-system added some absolute RPATH (absolute on + the build machine). Test if it's valid (does exist). + "rootDir"/"dirName" exists: + The original build-system already took care of setting an absolute + RPATH (absolute in the final rootfs). Resolve it and test if it's + valid (does exist). + "dirName" points somewhere else: + (can be anywhere: build trees, staging tree, host location, + non-existing location, etc.). Just discard such a path. */ + if (!dirName.compare(0, 7, "$ORIGIN")) { + string path = fileDir + dirName.substr(7); + if (!absolutePathExists(path, canonicalPath)) { + debug("removing directory '%s' from RPATH because '%s' doesn't exist\n", + dirName.c_str(), path.c_str()); + continue; + } + } else if (!dirName.compare(0, rootDir.length(), rootDir)) { + if (!absolutePathExists(dirName, canonicalPath)) { + debug("removing directory '%s' from RPATH because it doesn't exist\n", dirName.c_str()); + continue; + } + } else { + string path = rootDir + dirName; + if (!absolutePathExists(path, canonicalPath)) { + debug("removing directory '%s' from RPATH because it's not in rootdir\n", + dirName.c_str()); + continue; + } + } + + if (noStandardLibDirs) { + if (!canonicalPath.compare(rootDir + "/lib") || + !canonicalPath.compare(rootDir + "/usr/lib")) { + debug("removing directory '%s' from RPATH because it's a standard library directory\n", + dirName.c_str()); + continue; + } + } + + if (!libFoundInRPath(canonicalPath, neededLibs, neededLibFound)) { + debug("removing directory '%s' from RPATH because it does not contain needed libs\n", + dirName.c_str()); + continue; + } + + /* Finally make "canonicalPath" relative to "filedir" in "rootDir" */ + if (relativeToFile) + concatToRPath(newRPath, makePathRelative(canonicalPath, fileDir)); + else + concatToRPath(newRPath, canonicalPath.substr(rootDir.length())); + debug("keeping relative path of %s\n", canonicalPath.c_str()); + } + } + if (op == rpRemove) { if (!rpath) { debug("no RPATH to delete\n"); @@ -1413,7 +1549,9 @@ static bool shrinkRPath = false; static bool removeRPath = false; static bool setRPath = false; static bool printRPath = false; +static bool makeRPathRelative = false; static string newRPath; +static string rootDir; static set neededLibsToRemove; static map neededLibsToReplace; static set neededLibsToAdd; @@ -1438,14 +1576,16 @@ static void patchElf2(ElfFile & elfFile) elfFile.setInterpreter(newInterpreter); if (printRPath) - elfFile.modifyRPath(elfFile.rpPrint, ""); + elfFile.modifyRPath(elfFile.rpPrint, "", ""); if (shrinkRPath) - elfFile.modifyRPath(elfFile.rpShrink, ""); + elfFile.modifyRPath(elfFile.rpShrink, "", ""); else if (removeRPath) - elfFile.modifyRPath(elfFile.rpRemove, ""); + elfFile.modifyRPath(elfFile.rpRemove, "", ""); else if (setRPath) - elfFile.modifyRPath(elfFile.rpSet, newRPath); + elfFile.modifyRPath(elfFile.rpSet, "", newRPath); + else if (makeRPathRelative) + elfFile.modifyRPath(elfFile.rpMakeRelative, rootDir, ""); if (printNeeded) elfFile.printNeededLibs(); @@ -1508,6 +1648,9 @@ void showHelp(const string & progName) [--set-rpath RPATH]\n\ [--remove-rpath]\n\ [--shrink-rpath]\n\ + [--make-rpath-relative ROOTDIR]\n\ + [--no-standard-lib-dirs]\n\ + [--relative-to-file]\n\ [--print-rpath]\n\ [--force-rpath]\n\ [--add-needed LIBRARY]\n\ @@ -1564,6 +1707,17 @@ int main(int argc, char * * argv) setRPath = true; newRPath = argv[i]; } + else if (arg == "--make-rpath-relative") { + if (++i == argc) error("missing argument to --make-rpath-relative"); + makeRPathRelative = true; + rootDir = argv[i]; + } + else if (arg == "--no-standard-lib-dirs") { + noStandardLibDirs = true; + } + else if (arg == "--relative-to-file") { + relativeToFile = true; + } else if (arg == "--print-rpath") { printRPath = true; } -- 1.9.1