diff --git a/llvm/test/tools/llvm-objcopy/MachO/install-name-tool-rpath.test b/llvm/test/tools/llvm-objcopy/MachO/install-name-tool-rpath.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/MachO/install-name-tool-rpath.test @@ -0,0 +1,142 @@ +## 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/.' diff --git a/llvm/tools/llvm-objcopy/CopyConfig.h b/llvm/tools/llvm-objcopy/CopyConfig.h --- a/llvm/tools/llvm-objcopy/CopyConfig.h +++ b/llvm/tools/llvm-objcopy/CopyConfig.h @@ -178,6 +178,7 @@ std::vector DumpSection; std::vector SymbolsToAdd; std::vector RPathToAdd; + std::vector> RPathsToUpdate; DenseSet RPathsToRemove; // Section matchers diff --git a/llvm/tools/llvm-objcopy/CopyConfig.cpp b/llvm/tools/llvm-objcopy/CopyConfig.cpp --- a/llvm/tools/llvm-objcopy/CopyConfig.cpp +++ b/llvm/tools/llvm-objcopy/CopyConfig.cpp @@ -827,6 +827,13 @@ 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); @@ -860,6 +867,43 @@ 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 &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 Positional; for (auto Arg : InputArgs.filtered(INSTALL_NAME_TOOL_UNKNOWN)) return createStringError(errc::invalid_argument, "unknown argument '%s'", diff --git a/llvm/tools/llvm-objcopy/InstallNameToolOpts.td b/llvm/tools/llvm-objcopy/InstallNameToolOpts.td --- a/llvm/tools/llvm-objcopy/InstallNameToolOpts.td +++ b/llvm/tools/llvm-objcopy/InstallNameToolOpts.td @@ -21,5 +21,8 @@ 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.">; diff --git a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp --- a/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp +++ b/llvm/tools/llvm-objcopy/MachO/MachOObjcopy.cpp @@ -22,16 +22,31 @@ using SectionPred = std::function &Sec)>; using LoadCommandPred = std::function; +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(LC.Payload.data()), + LC.Payload.size()) + .rtrim('\0'); +} + static Error removeLoadCommands(const CopyConfig &Config, Object &Obj) { DenseSet 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(LC.Payload.data()), - LC.Payload.size()) - .rtrim('\0'); + StringRef RPath = getPayloadString(LC); if (RPathsToRemove.count(RPath)) { RPathsToRemove.erase(RPath); return true; @@ -116,6 +131,18 @@ Obj.SymTable.removeSymbols(RemovePred); } +template +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; @@ -257,12 +284,35 @@ 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(*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(LC.Payload.data()), - LC.Payload.size()) - .trim(0)) { + RPath == getPayloadString(LC)) { return createStringError(errc::invalid_argument, "rpath " + RPath + " would create a duplicate load command");