[llvm] [llvm-objcopy][ELF] Add an option to remove notes (PR #118739)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Dec 4 20:24:12 PST 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-binary-utilities
Author: Igor Kudrin (igorkudrin)
<details>
<summary>Changes</summary>
This adds an option `--remove-note` to selectively delete notes in an ELF file. For now, it is expected to be useful for editing core dump files; in particular, it searches for the notes in `PT_NOTE` segments and does not handle nested segments. The implementation can be extended later as needed.
---
Full diff: https://github.com/llvm/llvm-project/pull/118739.diff
7 Files Affected:
- (modified) llvm/include/llvm/ObjCopy/ELF/ELFConfig.h (+9)
- (modified) llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp (+96)
- (modified) llvm/lib/ObjCopy/ELF/ELFObject.cpp (+21)
- (modified) llvm/lib/ObjCopy/ELF/ELFObject.h (+5)
- (added) llvm/test/tools/llvm-objcopy/ELF/remove-note.test (+98)
- (modified) llvm/tools/llvm-objcopy/ObjcopyOptions.cpp (+39)
- (modified) llvm/tools/llvm-objcopy/ObjcopyOpts.td (+4)
``````````diff
diff --git a/llvm/include/llvm/ObjCopy/ELF/ELFConfig.h b/llvm/include/llvm/ObjCopy/ELF/ELFConfig.h
index 59960b65307430..01a8762cfb9c37 100644
--- a/llvm/include/llvm/ObjCopy/ELF/ELFConfig.h
+++ b/llvm/include/llvm/ObjCopy/ELF/ELFConfig.h
@@ -15,6 +15,12 @@
namespace llvm {
namespace objcopy {
+// Note to remove info specified by --remove-note option.
+struct RemoveNoteInfo {
+ StringRef Name;
+ uint32_t TypeId;
+};
+
// ELF specific configuration for copying/stripping a single file.
struct ELFConfig {
uint8_t NewSymbolVisibility = (uint8_t)ELF::STV_DEFAULT;
@@ -31,6 +37,9 @@ struct ELFConfig {
bool KeepFileSymbols = false;
bool LocalizeHidden = false;
bool VerifyNoteSections = true;
+
+ // Notes specified by --remove-note option.
+ SmallVector<RemoveNoteInfo, 0> NotesToRemove;
};
} // namespace objcopy
diff --git a/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp b/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp
index 4793651f1d4e0b..1df64b0b7ce886 100644
--- a/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp
+++ b/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp
@@ -609,6 +609,97 @@ static void addSymbol(Object &Obj, const NewSymbolInfo &SymInfo,
Sec ? (uint16_t)SYMBOL_SIMPLE_INDEX : (uint16_t)SHN_ABS, 0);
}
+template <class ELFT>
+static Error removeNoteImpl(Object &Obj,
+ ArrayRef<RemoveNoteInfo> NotesToRemove) {
+ LLVM_ELF_IMPORT_TYPES_ELFT(ELFT);
+ for (Segment &Seg : Obj.segments()) {
+ // TODO: Support nested segments
+ if (Seg.Type != PT_NOTE || Seg.ParentSegment)
+ continue;
+
+ // Find chunks of the segment data to remove
+ struct DeletedRange {
+ uint64_t OldFrom;
+ uint64_t OldTo;
+ uint64_t NewOffset;
+ };
+ std::vector<DeletedRange> DataToRemove;
+ ArrayRef<uint8_t> OldData = Seg.getContents();
+ size_t Align = std::max<size_t>(4, Seg.Align);
+ uint64_t Offset = 0;
+ while (Offset + sizeof(Elf_Nhdr) <= OldData.size()) {
+ auto Nhdr = reinterpret_cast<const Elf_Nhdr *>(OldData.data() + Offset);
+ size_t FullSize = Nhdr->getSize(Align);
+ if (Offset + FullSize > OldData.size())
+ break;
+ Elf_Note Note(*Nhdr);
+ if (llvm::any_of(NotesToRemove, [&](const RemoveNoteInfo &NoteInfo) {
+ return NoteInfo.TypeId == Note.getType() &&
+ (NoteInfo.Name.empty() || NoteInfo.Name == Note.getName());
+ }))
+ DataToRemove.push_back({Offset, Offset + FullSize, 0});
+ Offset += FullSize;
+ }
+ if (DataToRemove.empty())
+ continue;
+
+ // Prepare the new segment data
+ std::vector<uint8_t> NewData;
+ NewData.reserve(OldData.size());
+ Offset = 0;
+ for (auto &RemRange : DataToRemove) {
+ if (Offset < RemRange.OldFrom) {
+ auto Slice = OldData.slice(Offset, RemRange.OldFrom - Offset);
+ NewData.insert(NewData.end(), Slice.begin(), Slice.end());
+ }
+ RemRange.NewOffset = NewData.size();
+ Offset = RemRange.OldTo;
+ }
+ if (Offset < OldData.size()) {
+ auto Slice = OldData.slice(Offset);
+ NewData.insert(NewData.end(), Slice.begin(), Slice.end());
+ }
+
+ auto CalculateNewOffset = [&](uint64_t SecOffset) {
+ uint64_t Offset = SecOffset - Seg.Offset;
+ auto It =
+ llvm::upper_bound(DataToRemove, Offset,
+ [](const uint64_t &Off, const DeletedRange &Range) {
+ return Off < Range.OldFrom;
+ });
+ if (It != DataToRemove.begin()) {
+ --It;
+ Offset = (Offset > It->OldTo) ? (Offset - It->OldTo + It->NewOffset)
+ : It->NewOffset;
+ }
+ return Offset + Seg.Offset;
+ };
+
+ // Remap the segment's sections
+ DenseMap<const SectionBase *, std::pair<uint64_t, uint64_t>> Mapping;
+ for (const SectionBase *Sec : Seg.Sections) {
+ uint64_t NewOffset = CalculateNewOffset(Sec->Offset);
+ uint64_t NewSize =
+ CalculateNewOffset(Sec->Offset + Sec->Size) - NewOffset;
+ Mapping.try_emplace(Sec, NewOffset, NewSize);
+ }
+
+ Obj.updateSegmentData(Seg, std::move(NewData), Mapping);
+ }
+ return Error::success();
+}
+
+static Error removeNote(Object &Obj, endianness Endianness,
+ ArrayRef<RemoveNoteInfo> NotesToRemove) {
+ // Note: notes for both 32-bit and 64-bit ELF files use 4-byte words in the
+ // header, so the parsers are the same.
+ if (Endianness == endianness::little)
+ return removeNoteImpl<ELF64LE>(Obj, NotesToRemove);
+ else
+ return removeNoteImpl<ELF64BE>(Obj, NotesToRemove);
+}
+
static Error
handleUserSection(const NewSectionInfo &NewSection,
function_ref<Error(StringRef, ArrayRef<uint8_t>)> F) {
@@ -799,6 +890,11 @@ static Error handleArgs(const CommonConfig &Config, const ELFConfig &ELFConfig,
? endianness::little
: endianness::big;
+ if (!ELFConfig.NotesToRemove.empty()) {
+ if (Error Err = removeNote(Obj, E, ELFConfig.NotesToRemove))
+ return Err;
+ }
+
for (const NewSectionInfo &AddedSection : Config.AddSection) {
auto AddSection = [&](StringRef Name, ArrayRef<uint8_t> Data) -> Error {
OwnedDataSection &NewSection =
diff --git a/llvm/lib/ObjCopy/ELF/ELFObject.cpp b/llvm/lib/ObjCopy/ELF/ELFObject.cpp
index 01c2f24629077a..9f460f685706c8 100644
--- a/llvm/lib/ObjCopy/ELF/ELFObject.cpp
+++ b/llvm/lib/ObjCopy/ELF/ELFObject.cpp
@@ -2308,6 +2308,27 @@ Error Object::addNewSymbolTable() {
return Error::success();
}
+void Object::updateSegmentData(
+ Segment &S, std::vector<uint8_t> NewSegmentData,
+ const DenseMap<const SectionBase *, std::pair<uint64_t, uint64_t>>
+ &SectionMapping) {
+ auto It =
+ UpdatedSegments.insert_or_assign(&S, std::move(NewSegmentData)).first;
+ S.Contents = It->second;
+ S.FileSize = S.Contents.size();
+ if (S.MemSize)
+ S.MemSize = S.FileSize;
+ assert(SectionMapping.size() == S.Sections.size());
+ for (const auto &SM : SectionMapping) {
+ assert(SM.first->ParentSegment == &S && S.Sections.count(SM.first));
+ assert(SM.second.first >= S.Offset);
+ assert((SM.second.first + SM.second.second) <= (S.Offset + S.FileSize));
+ SectionBase *MutSec = const_cast<SectionBase *>(SM.first);
+ MutSec->Offset = SM.second.first;
+ MutSec->Size = SM.second.second;
+ }
+}
+
// Orders segments such that if x = y->ParentSegment then y comes before x.
static void orderSegments(std::vector<Segment *> &Segments) {
llvm::stable_sort(Segments, compareSegmentsByOffset);
diff --git a/llvm/lib/ObjCopy/ELF/ELFObject.h b/llvm/lib/ObjCopy/ELF/ELFObject.h
index 6ccf85387131e4..5e16d4c0c1885a 100644
--- a/llvm/lib/ObjCopy/ELF/ELFObject.h
+++ b/llvm/lib/ObjCopy/ELF/ELFObject.h
@@ -1159,6 +1159,7 @@ class Object {
std::vector<SegPtr> Segments;
std::vector<SecPtr> RemovedSections;
DenseMap<SectionBase *, std::vector<uint8_t>> UpdatedSections;
+ DenseMap<Segment *, std::vector<uint8_t>> UpdatedSegments;
static bool sectionIsAlloc(const SectionBase &Sec) {
return Sec.Flags & ELF::SHF_ALLOC;
@@ -1234,6 +1235,10 @@ class Object {
Segments.emplace_back(std::make_unique<Segment>(Data));
return *Segments.back();
}
+ void updateSegmentData(
+ Segment &S, std::vector<uint8_t> NewSegmentData,
+ const DenseMap<const SectionBase *, std::pair<uint64_t, uint64_t>>
+ &SectionMapping);
bool isRelocatable() const {
return (Type != ELF::ET_DYN && Type != ELF::ET_EXEC) || MustBeRelocatable;
}
diff --git a/llvm/test/tools/llvm-objcopy/ELF/remove-note.test b/llvm/test/tools/llvm-objcopy/ELF/remove-note.test
new file mode 100644
index 00000000000000..b24538e26d019e
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/ELF/remove-note.test
@@ -0,0 +1,98 @@
+# RUN: not llvm-objcopy --remove-note= - 2>&1 | FileCheck %s --check-prefix=ERR-NOTYPEID
+# RUN: not llvm-objcopy --remove-note=/1 - 2>&1 | FileCheck %s --check-prefix=ERR-EMPTYNAME
+# RUN: not llvm-objcopy --remove-note=CORE/1/2 - 2>&1 | FileCheck %s --check-prefix=ERR-INVNUM1
+# RUN: not llvm-objcopy --remove-note=Notanumber - 2>&1 | FileCheck %s --check-prefix=ERR-INVNUM2
+# RUN: not llvm-objcopy --remove-note=CORE/Notanumber - 2>&1 | FileCheck %s --check-prefix=ERR-INVNUM2
+
+# ERR-NOTYPEID: error: bad format for --remove-note, missing type_id
+# ERR-EMPTYNAME: error: bad format for --remove-note, note name is empty
+# ERR-INVNUM1: error: bad note type_id for --remove-note: '1/2'
+# ERR-INVNUM2: error: bad note type_id for --remove-note: 'Notanumber'
+
+# RUN: yaml2obj -D ALIGN=8 %s -o - \
+# RUN: | llvm-objcopy --remove-note=0x01 --remove-note=DUMMY/0x02 --remove-note=CORE/0x03 - - \
+# RUN: | llvm-readobj --segments --sections --notes - \
+# RUN: | FileCheck %s -D#SIZE=64
+
+# RUN: yaml2obj -D ALIGN=4 %s -o - \
+# RUN: | llvm-objcopy --remove-note=0x01 --remove-note=DUMMY/0x02 --remove-note=CORE/0x03 - - \
+# RUN: | llvm-readobj --segments --sections --notes - \
+# RUN: | FileCheck %s -D#SIZE=48
+
+# CHECK: Sections [
+# CHECK: Section {
+# CHECK: Name: .note
+# CHECK-NEXT: Type: SHT_NOTE
+# CHECK-NEXT: Flags [
+# CHECK-NEXT: ]
+# CHECK-NEXT: Address:
+# CHECK-NEXT: Offset: [[OFFSET:0x.+]]
+# CHECK-NEXT: Size: [[#%d,SIZE]]
+
+# CHECK: ProgramHeaders [
+# CHECK-NEXT: ProgramHeader {
+# CHECK-NEXT: Type: PT_NOTE
+# CHECK-NEXT: Offset: [[OFFSET]]
+# CHECK-NEXT: VirtualAddress: 0x0
+# CHECK-NEXT: PhysicalAddress: 0x0
+# CHECK-NEXT: FileSize: [[#%d,SIZE]]
+# CHECK-NEXT: MemSize: 0
+
+# CHECK: NoteSections [
+# CHECK-NEXT: NoteSection {
+# CHECK-NEXT: Name:
+# CHECK-NEXT: Offset: [[OFFSET]]
+# CHECK-NEXT: Size: 0x[[#%x,SIZE]]
+# CHECK-NEXT: Notes [
+# CHECK-NEXT: {
+# CHECK-NEXT: Owner: CORE
+# CHECK-NEXT: Data size: 0x2
+# CHECK-NEXT: Type: NT_FPREGSET
+# CHECK-NEXT: Description data (
+# CHECK-NEXT: 0000: 0202
+# CHECK-NEXT: )
+# CHECK-NEXT: }
+# CHECK-NEXT: {
+# CHECK-NEXT: Owner: CORE
+# CHECK-NEXT: Data size: 0x2
+# CHECK-NEXT: Type: NT_TASKSTRUCT
+# CHECK-NEXT: Description data (
+# CHECK-NEXT: 0000: 0404
+# CHECK-NEXT: )
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_CORE
+ Machine: EM_X86_64
+ProgramHeaders:
+ - Type: PT_NOTE
+ MemSize: 0
+ FirstSec: .note
+ LastSec: .note
+Sections:
+ - Name: .note
+ Type: SHT_NOTE
+ AddressAlign: [[ALIGN]]
+ Notes:
+ - Name: CORE
+ Type: 0x01
+ Desc: 0101
+ - Name: CORE
+ Type: 0x02
+ Desc: 0202
+ - Name: CORE
+ Type: 0x03
+ Desc: 0303
+ - Name: CORE
+ Type: 0x04
+ Desc: 0404
+ - Name: LINUX
+ Type: 0x01
+ Desc: 0505
+...
diff --git a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
index 104d802b1e1eeb..5e348d65adca18 100644
--- a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
+++ b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
@@ -527,6 +527,37 @@ static Expected<NewSymbolInfo> parseNewSymbolInfo(StringRef FlagValue) {
return SI;
}
+static Expected<RemoveNoteInfo> parseRemoveNoteInfo(StringRef FlagValue) {
+ // Parse value given with --remove-note option. The format is:
+ //
+ // [name/]type_id
+ //
+ // where:
+ // <name> - optional note name. If not given, all notes with the specified
+ // <type_id> are removed.
+ // <type_id> - note type value, can be decimal or hexadecimal number prefixed
+ // with 0x.
+ RemoveNoteInfo NI;
+ if (FlagValue.empty())
+ return createStringError(errc::invalid_argument,
+ "bad format for --remove-note, missing type_id");
+ SmallVector<StringRef, 2> Tokens;
+ FlagValue.split(Tokens, '/', /*MaxSplit=*/1);
+ assert(!Tokens.empty() && Tokens.size() <= 2);
+ if (Tokens.size() == 2) {
+ if (Tokens[0].empty())
+ return createStringError(
+ errc::invalid_argument,
+ "bad format for --remove-note, note name is empty");
+ NI.Name = Tokens[0];
+ }
+ if (Tokens.back().getAsInteger(0, NI.TypeId))
+ return createStringError(errc::invalid_argument,
+ "bad note type_id for --remove-note: '%s'",
+ Tokens.back().str().c_str());
+ return NI;
+}
+
// Parse input option \p ArgValue and load section data. This function
// extracts section name and name of the file keeping section data from
// ArgValue, loads data from the file, and stores section name and data
@@ -1210,6 +1241,14 @@ objcopy::parseObjcopyOptions(ArrayRef<const char *> ArgsArr,
};
}
+ for (auto *Arg : InputArgs.filtered(OBJCOPY_remove_note)) {
+ Expected<RemoveNoteInfo> NoteInfo = parseRemoveNoteInfo(Arg->getValue());
+ if (!NoteInfo)
+ return NoteInfo.takeError();
+
+ ELFConfig.NotesToRemove.push_back(*NoteInfo);
+ }
+
if (Config.DecompressDebugSections &&
Config.CompressionType != DebugCompressionType::None) {
return createStringError(
diff --git a/llvm/tools/llvm-objcopy/ObjcopyOpts.td b/llvm/tools/llvm-objcopy/ObjcopyOpts.td
index 434b5ff92324eb..fbc6a59d9461e7 100644
--- a/llvm/tools/llvm-objcopy/ObjcopyOpts.td
+++ b/llvm/tools/llvm-objcopy/ObjcopyOpts.td
@@ -297,3 +297,7 @@ defm pad_to
"of zero or the value specified by the --gap-fill option. "
"This option is only supported for ELF input and binary output">,
MetaVarName<"address">;
+
+defm remove_note
+ : Eq<"remove-note", "Remove note(s) with <type_id> and optional <name>">,
+ MetaVarName<"[name/]type_id">;
``````````
</details>
https://github.com/llvm/llvm-project/pull/118739
More information about the llvm-commits
mailing list