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,111 @@ +## 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 + +# RUN: llvm-objcopy %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 + +# 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 + +# 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 + +# 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 + +# 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 + +# 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 + +# COMBINED-ADD: cannot specify both -add_rpath @executable_e/. and -rpath @executable_e/. EE/. + +## Missing a RPath argument: +# RUN: not llvm-install-name-tool %t -rpath @executable_e/. 2>&1 | \ +# RUN: FileCheck %s --check-prefix=MISSING + +# MISSING: missing argument to -rpath option + +## Check that binary is not modified after errors: +# RUN: cmp %t %t1 + +--- !mach-o +FileHeader: + magic: 0xFEEDFACF + cputype: 0x01000007 + cpusubtype: 0x00000003 + filetype: 0x00000001 + ncmds: 4 + sizeofcmds: 128 + 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 @@ -806,6 +806,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(), "llvm-install-name-tool"); exit(1); @@ -839,6 +846,53 @@ Config.RPathsToRemove.insert(RPath); } + for (auto Arg : InputArgs.filtered(INSTALL_NAME_TOOL_rpath)) { + StringRef OldRPath = Arg->getValue(0); + StringRef NewRPath = Arg->getValue(1); + + // Cannot specify duplicate -rpath entries + auto It1 = std::find_if( + adl_begin(Config.RPathsToUpdate), adl_end(Config.RPathsToUpdate), + [&](const std::pair &RPair) { + return RPair.first == OldRPath || RPair.first == NewRPath || + RPair.second == OldRPath || RPair.second == NewRPath; + }); + if (It1 != adl_end(Config.RPathsToUpdate)) + 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(), + OldRPath.str().c_str(), NewRPath.str().c_str()); + + auto IsValidWith = [&OldRPath, &NewRPath ]( + Iterator First, Iterator Last, const char *ErrorMsg) + ->llvm::Error { + auto It2 = std::find_if(First, Last, [&](const StringRef &Str) { + return Str == OldRPath || Str == NewRPath; + }); + if (It2 != Last) + return createStringError(errc::invalid_argument, ErrorMsg, + It2->str().c_str(), OldRPath.str().c_str(), + NewRPath.str().c_str()); + + return Error::success(); + }; + + // Cannot specify the same rpath under both -delete_rpath and -rpath + if (Error E = IsValidWith( + adl_begin(Config.RPathsToRemove), adl_end(Config.RPathsToRemove), + "cannot specify both -delete_rpath %s and -rpath %s %s")) + return std::move(E); + + // Cannot specify the same rpath under both -add_rpath and -rpath + if (Error E = IsValidWith( + adl_begin(Config.RPathToAdd), adl_end(Config.RPathToAdd), + "cannot specify both -add_rpath %s and -rpath %s %s")) + return std::move(E); + + Config.RPathsToUpdate.push_back(std::make_pair(OldRPath, NewRPath)); + } + 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 @@ -116,6 +116,15 @@ Obj.SymTable.removeSymbols(RemovePred); } +template +static void updateLoadCommandName(LoadCommand *LC, StringRef NewName) { + uint32_t NewCmdsize = alignTo(sizeof(T) + NewName.size() + 1, 8); + + LC->MachOLoadCommand.load_command_data.cmdsize = NewCmdsize; + LC->Payload.assign(NewCmdsize - sizeof(T), 0); + std::copy(NewName.begin(), NewName.end(), LC->Payload.begin()); +} + static LoadCommand buildRPathLoadCommand(StringRef Path) { LoadCommand LC; MachO::rpath_command RPathLC; @@ -257,6 +266,35 @@ if (Error E = removeLoadCommands(Config, Obj)) return E; + for (std::pair RPair : Config.RPathsToUpdate) { + StringRef OldRPath = RPair.first; + StringRef NewRPath = RPair.second; + + LoadCommand *ToBeModifiedLC = NULL; + for (LoadCommand &LC : Obj.LoadCommands) { + if (LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_RPATH) { + StringRef CurrentRPath = + StringRef(reinterpret_cast(LC.Payload.data()), + LC.Payload.size()) + .rtrim('\0'); + + if (NewRPath == CurrentRPath) + return createStringError( + errc::invalid_argument, + "rpath " + NewRPath + " would create a duplicate load command"); + + if (OldRPath == CurrentRPath) + ToBeModifiedLC = &LC; + } + } + if (ToBeModifiedLC == NULL) + return createStringError(errc::invalid_argument, + "no LC_RPATH load command with path: " + + OldRPath); + + updateLoadCommandName(ToBeModifiedLC, NewRPath); + } + for (StringRef RPath : Config.RPathToAdd) { for (LoadCommand &LC : Obj.LoadCommands) { if (LC.MachOLoadCommand.load_command_data.cmd == MachO::LC_RPATH &&