Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
24308b2
[llvm-objcopy][ELF] Add an option to remove notes
igorkudrin Nov 28, 2024
6533806
Merge branch 'main' into llvm-objcopy-remove-note
igorkudrin Dec 14, 2024
3550f3a
fixup: update parsing the argument
igorkudrin Dec 14, 2024
51b05f2
fixup: update tests
igorkudrin Dec 14, 2024
1bad501
fixup: add the option to the command guide
igorkudrin Dec 14, 2024
3b17fdb
fixup: fix updating offsets
igorkudrin Dec 20, 2024
1c84760
fixup: split 'removeNote()'
igorkudrin Dec 20, 2024
f6d4330
fixup: do not allow '--remove-note' with '--(remove|add|update)-section'
igorkudrin Dec 20, 2024
fd72505
fixup: change ELF type of the test file ET_CORE -> ET_EXEC
igorkudrin Dec 21, 2024
e730cfd
fixup: also run the test for ELF32 and BE
igorkudrin Dec 21, 2024
de67454
fixup: handle SHT_NOTE sections outside of segments
igorkudrin Dec 21, 2024
95b6b94
Remove handling for segments
igorkudrin Dec 23, 2024
9baf37a
Remove DeletedRange.NewPos
igorkudrin Jan 16, 2025
1432460
"auto &RemRange" -> "const DeletedRange &RemRange"
igorkudrin Jan 16, 2025
a4c8edb
Remove a blank line at the end of an namespace
igorkudrin Jan 16, 2025
5695096
Print a warning for note segments and note sections in segments
igorkudrin Jan 16, 2025
17a3c77
Extend the test
igorkudrin Jan 17, 2025
08f7f23
Revert "Print a warning for note segments and note sections in segments"
igorkudrin Jan 18, 2025
1ee4850
removeNote() -> removeNotes()
igorkudrin Jan 18, 2025
2860c31
Print a warning for note segments and note sections in segments v2
igorkudrin Jan 18, 2025
b1957e7
std::vector<SecPtr>::iterator -> SecPtr&
igorkudrin Jan 18, 2025
9110b2c
Move tests from 'remove-note-unsupported.test' to 'remove-note.test'
igorkudrin Jan 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions llvm/include/llvm/ObjCopy/ELF/ELFConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
96 changes: 96 additions & 0 deletions llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 =
Expand Down
21 changes: 21 additions & 0 deletions llvm/lib/ObjCopy/ELF/ELFObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions llvm/lib/ObjCopy/ELF/ELFObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
98 changes: 98 additions & 0 deletions llvm/test/tools/llvm-objcopy/ELF/remove-note.test
Original file line number Diff line number Diff line change
@@ -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
...
39 changes: 39 additions & 0 deletions llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 4 additions & 0 deletions llvm/tools/llvm-objcopy/ObjcopyOpts.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">;
Loading