Index: llvm/test/tools/llvm-objcopy/bad-build-id.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-objcopy/bad-build-id.test @@ -0,0 +1,21 @@ +# RUN: yaml2obj %s > %t +# RUN: not llvm-objcopy --build-id-link-dir=%t-dir --build-id-link-input=.debug %t 2>&1 >/dev/null | FileCheck %s + +# CHECK: build ID size is too small + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .note.gnu.build-id + Type: SHT_NOTE + Flags: [ SHF_ALLOC ] + Content: 040000000100000003000000474E55004F000000 +ProgramHeaders: + - Type: PT_NOTE + Flags: [ PF_R ] + Sections: + - Section: .note.gnu.build-id Index: llvm/test/tools/llvm-objcopy/build-id-link-dir.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-objcopy/build-id-link-dir.test @@ -0,0 +1,51 @@ +# RUN: yaml2obj %s > %t +# RUN: mkdir -p %t-dir + +# RUN: llvm-objcopy --build-id-link-dir=%t-dir %t %t2 +# RUN: not test -e %t-dir/4f/cb712aa6387724a9f465a32cd8c14b + +# RUN: llvm-objcopy --build-id-link-dir=%t-dir --build-id-link-input=.debug %t %t3 +# RUN: cmp %t-dir/4f/cb712aa6387724a9f465a32cd8c14b.debug %t +# RUN: rm %t-dir/4f/cb712aa6387724a9f465a32cd8c14b.debug + +# RUN: llvm-objcopy --build-id-link-dir=%t-dir --build-id-link-output= --strip-sections %t %t4 +# RUN: cmp %t-dir/4f/cb712aa6387724a9f465a32cd8c14b %t4 +# RUN: rm %t-dir/4f/cb712aa6387724a9f465a32cd8c14b + +# Linking the output of an inplace argument means that the file in the build-id +# directory will be hard-linked to the resulting file. +# RUN: cp %t %t5 +# RUN: llvm-objcopy --build-id-link-dir=%t-dir --build-id-link-output= --strip-sections %t5 +# RUN: cmp %t-dir/4f/cb712aa6387724a9f465a32cd8c14b %t5 +# RUN: rm %t-dir/4f/cb712aa6387724a9f465a32cd8c14b + +# Linking the input of an inplace argument means that the file in the build-id +# directory will be hard-linked to the original file. +# RUN: cp %t %t6 +# RUN: llvm-objcopy --build-id-link-dir=%t-dir --build-id-link-input=.debug --strip-sections %t6 +# RUN: cmp %t-dir/4f/cb712aa6387724a9f465a32cd8c14b.debug %t +# RUN: rm %t-dir/4f/cb712aa6387724a9f465a32cd8c14b.debug + +# You can use both at once. +# RUN: llvm-objcopy --build-id-link-dir=%t-dir --build-id-link-output= --build-id-link-input=.debug --strip-sections %t %t7 +# RUN: cmp %t-dir/4f/cb712aa6387724a9f465a32cd8c14b.debug %t +# RUN: cmp %t-dir/4f/cb712aa6387724a9f465a32cd8c14b %t7 +# RUN: rm %t-dir/4f/cb712aa6387724a9f465a32cd8c14b.debug +# RUN: rm %t-dir/4f/cb712aa6387724a9f465a32cd8c14b + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: + - Name: .note.gnu.build-id + Type: SHT_NOTE + Flags: [ SHF_ALLOC ] + Content: 040000001000000003000000474E55004FCB712AA6387724A9F465A32CD8C14B +ProgramHeaders: + - Type: PT_NOTE + Flags: [ PF_R ] + Sections: + - Section: .note.gnu.build-id Index: llvm/test/tools/llvm-objcopy/no-build-id.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-objcopy/no-build-id.test @@ -0,0 +1,12 @@ +# RUN: yaml2obj %s > %t +# RUN: not llvm-objcopy --build-id-link-dir=%t-dir --build-id-link-input=.debug %t 2>&1 >/dev/null | FileCheck %s + +# CHECK: Could not find build ID. + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +Sections: Index: llvm/test/tools/llvm-objcopy/no-build-id2.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-objcopy/no-build-id2.test @@ -0,0 +1,11 @@ +# RUN: yaml2obj %s > %t +# RUN: not llvm-objcopy --build-id-link-dir=%t-dir --build-id-link-input=.debug %t 2>&1 >/dev/null | FileCheck %s + +# CHECK: Could not find build ID. + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 Index: llvm/tools/llvm-objcopy/CopyConfig.h =================================================================== --- llvm/tools/llvm-objcopy/CopyConfig.h +++ llvm/tools/llvm-objcopy/CopyConfig.h @@ -51,6 +51,9 @@ // Advanced options StringRef AddGnuDebugLink; + StringRef BuildIdLinkDir; + Optional BuildIdLinkInput; + Optional BuildIdLinkOutput; StringRef SplitDWO; StringRef SymbolsPrefix; Index: llvm/tools/llvm-objcopy/CopyConfig.cpp =================================================================== --- llvm/tools/llvm-objcopy/CopyConfig.cpp +++ llvm/tools/llvm-objcopy/CopyConfig.cpp @@ -285,8 +285,13 @@ } } - Config.SplitDWO = InputArgs.getLastArgValue(OBJCOPY_split_dwo); Config.AddGnuDebugLink = InputArgs.getLastArgValue(OBJCOPY_add_gnu_debuglink); + Config.BuildIdLinkDir = InputArgs.getLastArgValue(OBJCOPY_build_id_link_dir); + if (InputArgs.hasArg(OBJCOPY_build_id_link_input)) + Config.BuildIdLinkInput = InputArgs.getLastArgValue(OBJCOPY_build_id_link_input); + if (InputArgs.hasArg(OBJCOPY_build_id_link_output)) + Config.BuildIdLinkOutput = InputArgs.getLastArgValue(OBJCOPY_build_id_link_output); + Config.SplitDWO = InputArgs.getLastArgValue(OBJCOPY_split_dwo); Config.SymbolsPrefix = InputArgs.getLastArgValue(OBJCOPY_prefix_symbols); for (auto Arg : InputArgs.filtered(OBJCOPY_redefine_symbol)) { Index: llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp =================================================================== --- llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp +++ llvm/tools/llvm-objcopy/ELF/ELFObjcopy.cpp @@ -10,8 +10,8 @@ #include "ELFObjcopy.h" #include "Buffer.h" #include "CopyConfig.h" -#include "llvm-objcopy.h" #include "Object.h" +#include "llvm-objcopy.h" #include "llvm/ADT/BitmaskEnum.h" #include "llvm/ADT/Optional.h" @@ -115,6 +115,74 @@ llvm_unreachable("Invalid output format"); } +template +static Expected> +findBuildID(const object::ELFFile &In) { + Error Err = Error::success(); + // Make sure this is unchecked and tangentially check that it's not an error. + if (Err) + llvm_unreachable("This error should have been a success"); + constexpr const char *ELF_NOTE_GNU = "GNU"; + for (const auto &Phdr : unwrapOrError(In.program_headers())) { + if (Phdr.p_type != PT_NOTE) + continue; + for (const auto &Note : In.notes(Phdr, Err)) { + if (Err) + return std::move(Err); + if (Note.getType() == NT_GNU_BUILD_ID && Note.getName() == ELF_NOTE_GNU) { + return Note.getDesc(); + } + } + } + if (Err) + return std::move(Err); + return make_error( + "Could not find build ID.", + std::make_error_code(std::errc::invalid_argument)); +} + +static Expected> +findBuildID(const object::ELFObjectFileBase &In) { + if (auto *O = dyn_cast>(&In)) { + return findBuildID(*O->getELFFile()); + } else if (auto *O = dyn_cast>(&In)) { + return findBuildID(*O->getELFFile()); + } else if (auto *O = dyn_cast>(&In)) { + return findBuildID(*O->getELFFile()); + } else if (auto *O = dyn_cast>(&In)) { + return findBuildID(*O->getELFFile()); + } + llvm_unreachable("Bad file format"); +} + +static void linkToBuildIdDir(StringRef Dir, StringRef ToLink, StringRef Suffix, + const object::ELFObjectFileBase &In) { + ArrayRef BuildIdBytes = unwrapOrError(findBuildID(In)); + if (BuildIdBytes.size() < 2) + error("build ID size is too small"); + + SmallString<128> Path = Dir; + sys::path::append(Path, llvm::toHex(BuildIdBytes[0], true)); + if (auto EC = sys::fs::create_directories(Path)) { + error("cannot create build ID link directory " + Path + ": " + + EC.message()); + return; + } + + sys::path::append(Path, + llvm::toHex(BuildIdBytes.slice(1), /*LowerCase*/ true)); + Path += Suffix; + auto EC = sys::fs::create_hard_link(ToLink, Path); + if (EC) { + // Hard linking failed, try to remove the file first if it exists. + if (sys::fs::exists(Path)) + sys::fs::remove(Path, true); + EC = sys::fs::create_hard_link(ToLink, Path); + if (EC) + error("cannot link " + Path + ": " + EC.message()); + } +} + static void splitDWOToFile(const CopyConfig &Config, const Reader &Reader, StringRef File, ElfType OutputElfType) { auto DWOFile = Reader.create(); @@ -494,11 +562,19 @@ ELFReader Reader(&In); std::unique_ptr Obj = Reader.create(); const ElfType OutputElfType = getOutputElfType(In); + if (!Config.BuildIdLinkDir.empty() && Config.BuildIdLinkInput) { + linkToBuildIdDir(Config.BuildIdLinkDir, Config.InputFilename, + Config.BuildIdLinkInput.getValue(), In); + } handleArgs(Config, *Obj, Reader, OutputElfType); std::unique_ptr Writer = createWriter(Config, *Obj, Out, OutputElfType); Writer->finalize(); Writer->write(); + if (!Config.BuildIdLinkDir.empty() && Config.BuildIdLinkOutput) { + linkToBuildIdDir(Config.BuildIdLinkDir, Config.OutputFilename, + Config.BuildIdLinkOutput.getValue(), In); + } } } // end namespace elf Index: llvm/tools/llvm-objcopy/ELF/Object.h =================================================================== --- llvm/tools/llvm-objcopy/ELF/Object.h +++ llvm/tools/llvm-objcopy/ELF/Object.h @@ -254,7 +254,6 @@ }; std::set Sections; - ArrayRef Contents; public: uint64_t Align; @@ -269,6 +268,7 @@ uint64_t OriginalOffset; Segment *ParentSegment = nullptr; + ArrayRef Contents; explicit Segment(ArrayRef Data) : Contents(Data) {} Segment() {} Index: llvm/tools/llvm-objcopy/ObjcopyOpts.td =================================================================== --- llvm/tools/llvm-objcopy/ObjcopyOpts.td +++ llvm/tools/llvm-objcopy/ObjcopyOpts.td @@ -164,3 +164,15 @@ def version : Flag<["-", "--"], "version">, HelpText<"Print the version and exit.">; +defm build_id_link_dir + : Eq<"build-id-link-dir", + "Use as by --build-id-link-input and --build-id-link-output">, + MetaVarName<"dir">; +defm build_id_link_input + : Eq<"build-id-link-input", "Hard-link the input to /xx/xxx " + "name derived from hex build ID">, + MetaVarName<"suffix">; +defm build_id_link_output + : Eq<"build-id-link-output", "Hard-link the output to /xx/xxx " + "name derived from hex build ID">, + MetaVarName<"suffix">;