--- /dev/null
+## This test checks updating a LC_RPATH load command in a MachO binary.
+
+# RUN: yaml2obj %s -o %t
+
+## Updating a single RPath entry:
+# RUN: llvm-install-name-tool -rpath @executable_a/. @executable_A/. %t
+# RUN: llvm-objdump -p %t | \
+# RUN: FileCheck %s --check-prefix=RPATHS --implicit-check-not=@executable
+
+# RPATHS: @executable_A/.
+# RPATHS: @executable_short_test
+# RPATHS: @executable_long_test/.
+# RPATHS: @executable_d/.
+
+## Updating multiple RPath entries:
+# RUN: llvm-install-name-tool -rpath @executable_short_test/. @executable_test/. \
+# RUN: -rpath @executable_long_test/. @executable_long_long_test/. %t
+# RUN: llvm-objdump -p %t | \
+# RUN: FileCheck %s --check-prefix=RPATHS-MULTIPLE --implicit-check-not=@executable
+
+# RPATHS-MULTIPLE: @executable_A/.
+# RPATHS-MULTIPLE: @executable_test/.
+# RPATHS-MULTIPLE: @executable_long_long_test/.
+# RPATHS-MULTIPLE: @executable_d/.
+
+## Check that cmdsize accounts for NULL terminator:
+# RUN: llvm-install-name-tool -rpath @executable_A/. ABCD %t
+# RUN: llvm-objdump -p %t | FileCheck %s --check-prefix=RPATH-SIZE
+
+# RPATH-SIZE: cmd LC_RPATH
+# RPATH-SIZE-NEXT: cmdsize 24
+# RPATH-SIZE-NEXT: path ABCD
+
+## Updating and adding different RPaths:
+# RUN: llvm-install-name-tool -rpath @executable_d/. @executable_D/. \
+# RUN: -add_rpath @executable_e/. %t
+# RUN: llvm-objdump -p %t | FileCheck %s --check-prefix=RPATHS-ADD --implicit-check-not=@executable
+
+# RPATHS-ADD: ABCD
+# RPATHS-ADD: @executable_test/.
+# RPATHS-ADD: @executable_long_long_test/.
+# RPATHS-ADD: @executable_D/.
+# RPATHS-ADD: @executable_e/.
+
+## Updating and deleting different RPaths:
+# RUN: llvm-install-name-tool -rpath @executable_D/. @executable_d/. \
+# RUN: -delete_rpath @executable_e/. %t
+# RUN: llvm-objdump -p %t | FileCheck %s --check-prefix=RPATHS-DELETE --implicit-check-not=@executable
+
+# RPATHS-DELETE: ABCD
+# RPATHS-DELETE: @executable_test/.
+# RPATHS-DELETE: @executable_long_long_test/.
+# RPATHS-DELETE: @executable_d/.
+
+# RUN: cp %t %t1
+
+## Updating multiple RPath entries where one exists and the other doesn't:
+# RUN: not llvm-install-name-tool -rpath @executable_test/. @executable/. \
+# RUN: -rpath @executable_long_test/. @executable_long_longest/. %t 2>&1 | \
+# RUN: FileCheck %s --check-prefix=RPATHS-FAIL
+# RUN: cmp %t %t1
+
+# RPATHS-FAIL: no LC_RPATH load command with path: @executable_long_test/.
+
+## Updating a nonexistent RPath:
+# RUN: not llvm-install-name-tool -rpath @executable_a/. @executable_AA/. %t 2>&1 | \
+# RUN: FileCheck %s --check-prefix=ABSENT-RPATH
+# RUN: cmp %t %t1
+
+# ABSENT-RPATH: no LC_RPATH load command with path: @executable_a/.
+
+## Updating to an existing RPath:
+# RUN: not llvm-install-name-tool -rpath @executable_d/. ABCD %t 2>&1 | \
+# RUN: FileCheck %s --check-prefix=EXISTING
+# RUN: cmp %t %t1
+
+# EXISTING: rpath ABCD would create a duplicate load command
+
+## Duplicate RPath entries:
+# RUN: not llvm-install-name-tool -rpath DDD1/. @exec_d/. \
+# RUN: -rpath @exec_d/. DDD2/. %t 2>&1 | \
+# RUN: FileCheck %s --check-prefix=DUPLICATE
+# RUN: cmp %t %t1
+
+# DUPLICATE: cannot specify both -rpath DDD1/. @exec_d/. and -rpath @exec_d/. DDD2/.
+
+## Updating and deleting RPath at the same time:
+# RUN: not llvm-install-name-tool -rpath @executable_d/. DD/. \
+# RUN: -delete_rpath @executable_d/. %t 2>&1 | \
+# RUN: FileCheck %s --check-prefix=COMBINED-DELETE
+# RUN: cmp %t %t1
+
+# COMBINED-DELETE: cannot specify both -delete_rpath @executable_d/. and -rpath @executable_d/. DD/.
+
+## Updating and adding RPath at the same time:
+# RUN: not llvm-install-name-tool -rpath @executable_e/. EE/. \
+# RUN: -add_rpath @executable_e/. %t 2>&1 | \
+# RUN: FileCheck %s --check-prefix=COMBINED-ADD
+# RUN: cmp %t %t1
+
+# COMBINED-ADD: cannot specify both -add_rpath @executable_e/. and -rpath @executable_e/. EE/.
+
+## Missing an RPath argument:
+# RUN: not llvm-install-name-tool %t -rpath @executable_e/. 2>&1 | \
+# RUN: FileCheck %s --check-prefix=MISSING
+# RUN: cmp %t %t1
+
+## Missing both RPath arguments:
+# RUN: not llvm-install-name-tool %t -rpath 2>&1 | \
+# RUN: FileCheck %s --check-prefix=MISSING
+# RUN: cmp %t %t1
+
+# MISSING: missing argument to -rpath option
+
+
+--- !mach-o
+FileHeader:
+ magic: 0xFEEDFACF
+ cputype: 0x01000007
+ cpusubtype: 0x00000003
+ filetype: 0x00000001
+ ncmds: 4
+ sizeofcmds: 144
+ flags: 0x00002000
+ reserved: 0x00000000
+LoadCommands:
+ - cmd: LC_RPATH
+ cmdsize: 32
+ path: 12
+ PayloadString: '@executable_a/.'
+ - cmd: LC_RPATH
+ cmdsize: 40
+ path: 12
+ PayloadString: '@executable_short_test/.'
+ - cmd: LC_RPATH
+ cmdsize: 40
+ path: 12
+ PayloadString: '@executable_long_test/.'
+ - cmd: LC_RPATH
+ cmdsize: 32
+ path: 12
+ PayloadString: '@executable_d/.'
llvm::opt::InputArgList InputArgs =
T.ParseArgs(ArgsArr, MissingArgumentIndex, MissingArgumentCount);
+ if (MissingArgumentCount)
+ return createStringError(
+ errc::invalid_argument,
+ "missing argument to " +
+ StringRef(InputArgs.getArgString(MissingArgumentIndex)) +
+ " option");
+
if (InputArgs.size() == 0) {
printHelp(T, errs(), ToolType::InstallNameTool);
exit(1);
Config.RPathsToRemove.insert(RPath);
}
+ for (auto *Arg : InputArgs.filtered(INSTALL_NAME_TOOL_rpath)) {
+ StringRef Old = Arg->getValue(0);
+ StringRef New = Arg->getValue(1);
+
+ auto Match = [=](StringRef RPath) { return RPath == Old || RPath == New; };
+
+ // Cannot specify duplicate -rpath entries
+ auto It1 = find_if(Config.RPathsToUpdate,
+ [&Match](const std::pair<StringRef, StringRef> &OldNew) {
+ return Match(OldNew.first) || Match(OldNew.second);
+ });
+ if (It1 != Config.RPathsToUpdate.end())
+ return createStringError(
+ errc::invalid_argument,
+ "cannot specify both -rpath %s %s and -rpath %s %s",
+ It1->first.str().c_str(), It1->second.str().c_str(),
+ Old.str().c_str(), New.str().c_str());
+
+ // Cannot specify the same rpath under both -delete_rpath and -rpath
+ auto It2 = find_if(Config.RPathsToRemove, Match);
+ if (It2 != Config.RPathsToRemove.end())
+ return createStringError(
+ errc::invalid_argument,
+ "cannot specify both -delete_rpath %s and -rpath %s %s",
+ It2->str().c_str(), Old.str().c_str(), New.str().c_str());
+
+ // Cannot specify the same rpath under both -add_rpath and -rpath
+ auto It3 = find_if(Config.RPathToAdd, Match);
+ if (It3 != Config.RPathToAdd.end())
+ return createStringError(
+ errc::invalid_argument,
+ "cannot specify both -add_rpath %s and -rpath %s %s",
+ It3->str().c_str(), Old.str().c_str(), New.str().c_str());
+
+ Config.RPathsToUpdate.emplace_back(Old, New);
+ }
+
SmallVector<StringRef, 2> Positional;
for (auto Arg : InputArgs.filtered(INSTALL_NAME_TOOL_UNKNOWN))
return createStringError(errc::invalid_argument, "unknown argument '%s'",
std::vector<StringRef> DumpSection;
std::vector<StringRef> SymbolsToAdd;
std::vector<StringRef> RPathToAdd;
+ std::vector<std::pair<StringRef, StringRef>> RPathsToUpdate;
DenseSet<StringRef> RPathsToRemove;
// Section matchers
def delete_rpath: Option<["-", "--"], "delete_rpath", KIND_SEPARATE>,
HelpText<"Delete specified rpath">;
+def rpath: MultiArg<["-", "--"], "rpath", 2>,
+ HelpText<"Change rpath path name">;
+
def version : Flag<["--"], "version">,
HelpText<"Print the version and exit.">;
using SectionPred = std::function<bool(const std::unique_ptr<Section> &Sec)>;
using LoadCommandPred = std::function<bool(const LoadCommand &LC)>;
+static bool isLoadCommandWithPayloadString(const LoadCommand &LC) {
+ // TODO: Add support for LC_REEXPORT_DYLIB, LC_LOAD_UPWARD_DYLIB and
+ // LC_LAZY_LOAD_DYLIB
+ return LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_RPATH ||
+ LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_ID_DYLIB ||
+ LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_LOAD_DYLIB ||
+ LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_LOAD_WEAK_DYLIB;
+}
+
+static StringRef getPayloadString(const LoadCommand &LC) {
+ assert(isLoadCommandWithPayloadString(LC) &&
+ "unsupported load command encountered");
+
+ return StringRef(reinterpret_cast<const char *>(LC.Payload.data()),
+ LC.Payload.size())
+ .rtrim('\0');
+}
+
static Error removeLoadCommands(const CopyConfig &Config, Object &Obj) {
DenseSet<StringRef> RPathsToRemove(Config.RPathsToRemove.begin(),
Config.RPathsToRemove.end());
LoadCommandPred RemovePred = [&RPathsToRemove](const LoadCommand &LC) {
if (LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_RPATH) {
- StringRef RPath =
- StringRef(reinterpret_cast<const char *>(LC.Payload.data()),
- LC.Payload.size())
- .rtrim('\0');
+ StringRef RPath = getPayloadString(LC);
if (RPathsToRemove.count(RPath)) {
RPathsToRemove.erase(RPath);
return true;
Obj.SymTable.removeSymbols(RemovePred);
}
+template <typename LCType>
+static void updateLoadCommandPayloadString(LoadCommand &LC, StringRef S) {
+ assert(isLoadCommandWithPayloadString(LC) &&
+ "unsupported load command encountered");
+
+ uint32_t NewCmdsize = alignTo(sizeof(LCType) + S.size() + 1, 8);
+
+ LC.MachOLoadCommand.load_command_data.cmdsize = NewCmdsize;
+ LC.Payload.assign(NewCmdsize - sizeof(LCType), 0);
+ std::copy(S.begin(), S.end(), LC.Payload.begin());
+}
+
static LoadCommand buildRPathLoadCommand(StringRef Path) {
LoadCommand LC;
MachO::rpath_command RPathLC;
if (Error E = removeLoadCommands(Config, Obj))
return E;
+ StringRef Old, New;
+ for (const auto &OldNew : Config.RPathsToUpdate) {
+ std::tie(Old, New) = OldNew;
+
+ auto FindRPathLC = [&Obj](StringRef RPath) {
+ return find_if(Obj.LoadCommands, [=](const LoadCommand &LC) {
+ return LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_RPATH &&
+ getPayloadString(LC) == RPath;
+ });
+ };
+
+ auto NewIt = FindRPathLC(New);
+ if (NewIt != Obj.LoadCommands.end())
+ return createStringError(errc::invalid_argument,
+ "rpath " + New +
+ " would create a duplicate load command");
+
+ auto OldIt = FindRPathLC(Old);
+ if (OldIt == Obj.LoadCommands.end())
+ return createStringError(errc::invalid_argument,
+ "no LC_RPATH load command with path: " + Old);
+
+ updateLoadCommandPayloadString<MachO::rpath_command>(*OldIt, New);
+ }
+
for (StringRef RPath : Config.RPathToAdd) {
for (LoadCommand &LC : Obj.LoadCommands) {
if (LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_RPATH &&
- RPath == StringRef(reinterpret_cast<char *>(LC.Payload.data()),
- LC.Payload.size())
- .trim(0)) {
+ RPath == getPayloadString(LC)) {
return createStringError(errc::invalid_argument,
"rpath " + RPath +
" would create a duplicate load command");