diff --git a/llvm/docs/CommandGuide/llvm-objcopy.rst b/llvm/docs/CommandGuide/llvm-objcopy.rst index 8ccb025a3f0f3..7f20a76550c83 100644 --- a/llvm/docs/CommandGuide/llvm-objcopy.rst +++ b/llvm/docs/CommandGuide/llvm-objcopy.rst @@ -303,6 +303,15 @@ them. Shift LMA of non-zero-sized segments by ````. +.. option:: --change-section-address
{=+-}, --adjust-section-vma + + Change the address of sections that match ``
`` pattern to the + specified value, or apply ``+``/``-`` to the current value. Can be + specified multiple times to specify multiple patterns. Each section is only + modified by one ``--change-section-address`` argument. If a section name + matches multiple patterns, the rightmost change applies. The object file needs + to be of ET_REL type. + .. option:: --change-start , --adjust-start Add ```` to the program's start address. Can be specified multiple diff --git a/llvm/include/llvm/ObjCopy/CommonConfig.h b/llvm/include/llvm/ObjCopy/CommonConfig.h index 7f9d90d528b3e..5ae09760e9a54 100644 --- a/llvm/include/llvm/ObjCopy/CommonConfig.h +++ b/llvm/include/llvm/ObjCopy/CommonConfig.h @@ -151,6 +151,18 @@ class NameMatcher { } }; +enum class AdjustKind { Set, Add, Subtract }; + +struct AddressUpdate { + uint64_t Value = 0; + AdjustKind Kind = AdjustKind::Add; +}; + +struct SectionPatternAddressUpdate { + NameMatcher SectionPattern; + AddressUpdate Update; +}; + enum class SymbolFlag { Global, Local, @@ -219,6 +231,7 @@ struct CommonConfig { SmallVector AddSection; SmallVector DumpSection; SmallVector UpdateSection; + SmallVector ChangeSectionAddress; // Section matchers NameMatcher KeepSection; diff --git a/llvm/lib/ObjCopy/ConfigManager.cpp b/llvm/lib/ObjCopy/ConfigManager.cpp index c542c4e5f0743..78fc0c451e1a3 100644 --- a/llvm/lib/ObjCopy/ConfigManager.cpp +++ b/llvm/lib/ObjCopy/ConfigManager.cpp @@ -26,7 +26,8 @@ Expected ConfigManager::getCOFFConfig() const { Common.DecompressDebugSections || Common.DiscardMode == DiscardType::Locals || !Common.SymbolsToAdd.empty() || Common.GapFill != 0 || - Common.PadTo != 0 || Common.ChangeSectionLMAValAll != 0) + Common.PadTo != 0 || Common.ChangeSectionLMAValAll != 0 || + !Common.ChangeSectionAddress.empty()) return createStringError(llvm::errc::invalid_argument, "option is not supported for COFF"); @@ -48,7 +49,8 @@ Expected ConfigManager::getMachOConfig() const { Common.DecompressDebugSections || Common.StripUnneeded || Common.DiscardMode == DiscardType::Locals || !Common.SymbolsToAdd.empty() || Common.GapFill != 0 || - Common.PadTo != 0 || Common.ChangeSectionLMAValAll != 0) + Common.PadTo != 0 || Common.ChangeSectionLMAValAll != 0 || + !Common.ChangeSectionAddress.empty()) return createStringError(llvm::errc::invalid_argument, "option is not supported for MachO"); @@ -68,7 +70,8 @@ Expected ConfigManager::getWasmConfig() const { !Common.SectionsToRename.empty() || !Common.SetSectionAlignment.empty() || !Common.SetSectionFlags.empty() || !Common.SetSectionType.empty() || !Common.SymbolsToRename.empty() || Common.GapFill != 0 || - Common.PadTo != 0 || Common.ChangeSectionLMAValAll != 0) + Common.PadTo != 0 || Common.ChangeSectionLMAValAll != 0 || + !Common.ChangeSectionAddress.empty()) return createStringError(llvm::errc::invalid_argument, "only flags for section dumping, removal, and " "addition are supported"); @@ -97,7 +100,8 @@ Expected ConfigManager::getXCOFFConfig() const { Common.StripDebug || Common.StripNonAlloc || Common.StripSections || Common.Weaken || Common.StripUnneeded || Common.DecompressDebugSections || Common.GapFill != 0 || Common.PadTo != 0 || - Common.ChangeSectionLMAValAll != 0) { + Common.ChangeSectionLMAValAll != 0 || + !Common.ChangeSectionAddress.empty()) { return createStringError( llvm::errc::invalid_argument, "no flags are supported yet, only basic copying is allowed"); diff --git a/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp b/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp index 075455c034154..40598861d4273 100644 --- a/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp +++ b/llvm/lib/ObjCopy/ELF/ELFObjcopy.cpp @@ -745,6 +745,56 @@ static Error handleArgs(const CommonConfig &Config, const ELFConfig &ELFConfig, } } + if (!Config.ChangeSectionAddress.empty()) { + if (Obj.Type != ELF::ET_REL) + return createStringError( + object_error::invalid_file_type, + "cannot change section address in a non-relocatable file"); + + StringMap SectionsToUpdateAddress; + for (const SectionPatternAddressUpdate &PatternUpdate : + make_range(Config.ChangeSectionAddress.rbegin(), + Config.ChangeSectionAddress.rend())) { + for (SectionBase &Sec : Obj.sections()) { + if (PatternUpdate.SectionPattern.matches(Sec.Name) && + SectionsToUpdateAddress.try_emplace(Sec.Name, PatternUpdate.Update) + .second) { + if (PatternUpdate.Update.Kind == AdjustKind::Subtract && + Sec.Addr < PatternUpdate.Update.Value) { + return createStringError( + errc::invalid_argument, + "address 0x" + Twine::utohexstr(Sec.Addr) + + " cannot be decreased by 0x" + + Twine::utohexstr(PatternUpdate.Update.Value) + + ". The result would underflow"); + } + if (PatternUpdate.Update.Kind == AdjustKind::Add && + Sec.Addr > std::numeric_limits::max() - + PatternUpdate.Update.Value) { + return createStringError( + errc::invalid_argument, + "address 0x" + Twine::utohexstr(Sec.Addr) + + " cannot be increased by 0x" + + Twine::utohexstr(PatternUpdate.Update.Value) + + ". The result would overflow"); + } + + switch (PatternUpdate.Update.Kind) { + case (AdjustKind::Set): + Sec.Addr = PatternUpdate.Update.Value; + break; + case (AdjustKind::Subtract): + Sec.Addr -= PatternUpdate.Update.Value; + break; + case (AdjustKind::Add): + Sec.Addr += PatternUpdate.Update.Value; + break; + } + } + } + } + } + if (Config.OnlyKeepDebug) for (auto &Sec : Obj.sections()) if (Sec.Flags & SHF_ALLOC && Sec.Type != SHT_NOTE) diff --git a/llvm/test/tools/llvm-objcopy/ELF/change-section-address.test b/llvm/test/tools/llvm-objcopy/ELF/change-section-address.test new file mode 100644 index 0000000000000..b17b1492690af --- /dev/null +++ b/llvm/test/tools/llvm-objcopy/ELF/change-section-address.test @@ -0,0 +1,199 @@ +## This test tests the behavior of --change-section-address option. + +# RUN: yaml2obj -DTYPE=REL %s -o %ti1 + +## Basic check that the option processes wildcards and changes the address as expected. +# RUN: llvm-objcopy --change-section-address *+0x20 %ti1 %to1 +# RUN: llvm-readelf --section-headers %to1 | FileCheck %s --check-prefix=CHECK-ADD-ALL + +## Check that --change-section-address alias --adjust-section-vma produces the same output as the test above. +# RUN: llvm-objcopy --adjust-section-vma *+0x20 %ti1 %to2 +# RUN: cmp %to1 %to2 + +## Check that negative adjustment reduces the address by the specified value. +# RUN: llvm-objcopy --change-section-address .anotherone-0x30 %ti1 %to3 +# RUN: llvm-readelf --section-headers %to3 | FileCheck %s --check-prefix=CHECK-SUB-SECTION + +## Check that a wildcard pattern works and only the specified sections are updated. +# RUN: llvm-objcopy --change-section-address .text*+0x20 %ti1 %to4 +# RUN: llvm-readelf --section-headers %to4 | FileCheck %s --check-prefix=CHECK-ADD-PATTERN + +## Check that regex pattern can be used with --change-section-address. +# RUN: llvm-objcopy --regex --change-section-address .text.+0x20 %ti1 %to5 +# RUN: llvm-readelf --section-headers %to5 | FileCheck %s --check-prefix=CHECK-ADD-PATTERN + +## Check that a section address can be set to a specific value. +# RUN: llvm-objcopy --change-section-address .text*=0x10 %ti1 %to6 +# RUN: llvm-readelf --section-headers %to6 | FileCheck %s --check-prefix=CHECK-SET-PATTERN + +## Check setting that a section address can be set to the maximum possible value (UINT64_MAX). +# RUN: llvm-objcopy --change-section-address .text2=0xffffffffffffffff %ti1 %to7 +# RUN: llvm-readelf --section-headers %to7 | FileCheck %s --check-prefix=CHECK-MAX + +## Check that a section address can be adjusted to the maximum possible value (UINT64_MAX). +# RUN: llvm-objcopy --change-section-address .text2+0xfffffffffffffdff %ti1 %to8 +# RUN: llvm-readelf --section-headers %to8 | FileCheck %s --check-prefix=CHECK-MAX + +## Check that the section address can be adjusted to the minimum possible value (0). +# RUN: llvm-objcopy --change-section-address .text2-0x200 %ti1 %to9 +# RUN: llvm-readelf --section-headers %to9 | FileCheck %s --check-prefix=CHECK-ZERO + +## Check that a section address can be adjusted by a maximum possible positive offset (UINT64_MAX). +# RUN: llvm-objcopy --change-section-address .text2=0 %ti1 %to10 +# RUN: llvm-objcopy --change-section-address .text2+0xffffffffffffffff %to10 %to11 +# RUN: llvm-readelf --section-headers %to11 | FileCheck %s --check-prefix=CHECK-MAX + +## Check that a section address can be adjusted by a maximum possible negative offset (UINT64_MIN). +# RUN: llvm-objcopy --change-section-address .text2=0xffffffffffffffff %ti1 %to12 +# RUN: llvm-objcopy --change-section-address .text2-0xffffffffffffffff %to12 %to13 +# RUN: llvm-readelf --section-headers %to13 | FileCheck %s --check-prefix=CHECK-ZERO + +## Check two independent changes. +# RUN: llvm-objcopy --change-section-address .text1=0x110 --change-section-address .text2=0x210 %ti1 %to14 +# RUN: llvm-readelf --section-headers %to14 | FileCheck %s --check-prefix=CHECK-INDEPENDENT + +## Check two overlapping changes. +# RUN: llvm-objcopy --change-section-address .anotherone-0x30 --change-section-address .anotherone+0x20 %ti1 %to15 +# RUN: llvm-readelf --section-headers %to15 | FileCheck %s --check-prefix=CHECK-USE-LAST + +## Check unused option. +# RUN: llvm-objcopy --change-section-address .anotherone=0x455 --change-section-address *+0x20 %ti1 %to16 +# RUN: llvm-readelf --section-headers %to16 | FileCheck %s --check-prefix=CHECK-NOTSUPERSET-SET + +## Check partial overlap (.anotherone overlaps). +# RUN: llvm-objcopy --change-section-address *+0x20 --change-section-address .anotherone=0x455 %ti1 %to17 +# RUN: llvm-readelf --section-headers %to17 | FileCheck %s --check-prefix=CHECK-SUPERSET-SET + +## Check more complex partial overlap (P1: .anotherone, .text2, P2: .text1, text2) (.text2 overlaps). +# RUN: llvm-objcopy --regex --change-section-address ".(text2|anotherone)+0x20" --change-section-address .text.*+0x30 %ti1 %to18 +# RUN: llvm-readelf --section-headers %to18 | FileCheck %s --check-prefix=CHECK-PARTIAL-OVERLAP + +# CHECK-ADD-ALL: [Nr] Name Type Address +# CHECK-ADD-ALL: .text1 +# CHECK-ADD-ALL-SAME: 0000000000000120 +# CHECK-ADD-ALL: .text2 +# CHECK-ADD-ALL-SAME: 0000000000000220 +# CHECK-ADD-ALL: .anotherone +# CHECK-ADD-ALL-SAME: 0000000000000320 +# CHECK-ADD-ALL: =a-b+c++d +# CHECK-ADD-ALL-SAME: 0000000000000420 +# CHECK-ADD-ALL: .strtab +# CHECK-ADD_ALL-SAME: 0000000000000020 +# CHECK-ADD-ALL: .shstrtab +# CHECK-ADD-ALL-SAME: 0000000000000020 + +# CHECK-SUB-SECTION: .text1 +# CHECK-SUB-SECTION-SAME: 0000000000000100 +# CHECK-SUB-SECTION: .text2 +# CHECK-SUB-SECTION-SAME: 0000000000000200 +# CHECK-SUB-SECTION: .anotherone +# CHECK-SUB-SECTION-SAME: 00000000000002d0 + +# CHECK-ADD-PATTERN: .text1 +# CHECK-ADD-PATTERN-SAME: 0000000000000120 +# CHECK-ADD-PATTERN: .text2 +# CHECK-ADD-PATTERN-SAME: 0000000000000220 +# CHECK-ADD-PATTERN: .anotherone +# CHECK-ADD-PATTERN-SAME: 0000000000000300 + +# CHECK-SET-PATTERN: .text1 +# CHECK-SET-PATTERN-SAME: 0000000000000010 +# CHECK-SET-PATTERN: .text2 +# CHECK-SET-PATTERN-SAME: 0000000000000010 +# CHECK-SET-PATTERN: .anotherone +# CHECK-SET-PATTERN-SAME: 0000000000000300 + +# CHECK-MAX: .text2 +# CHECK-MAX-SAME: ffffffffffffffff +# CHECK-ZERO: .text2 +# CHECK-ZERO-SAME: 0000000000000000 + +# CHECK-INDEPENDENT: .text1 +# CHECK-INDEPENDENT-SAME: 0000000000000110 +# CHECK-INDEPENDENT: .text2 +# CHECK-INDEPENDENT-SAME: 0000000000000210 + +# CHECK-USE-LAST: .anotherone +# CHECK-USE-LAST-SAME: 0000000000000320 + +# CHECK-NOTSUPERSET-SET: .text1 +# CHECK-NOTSUPERSET-SET-SAME: 0000000000000120 +# CHECK-NOTSUPERSET-SET: .text2 +# CHECK-NOTSUPERSET-SET-SAME: 0000000000000220 +# CHECK-NOTSUPERSET-SET: .anotherone +# CHECK-NOTSUPERSET-SET-SAME: 0000000000000320 + +# CHECK-SUPERSET-SET: .text1 +# CHECK-SUPERSET-SET-SAME: 0000000000000120 +# CHECK-SUPERSET-SET: .text2 +# CHECK-SUPERSET-SET-SAME: 0000000000000220 +# CHECK-SUPERSET-SET: .anotherone +# CHECK-SUPERSET-SET-SAME: 0000000000000455 + +# CHECK-PARTIAL-OVERLAP: .text1 +# CHECK-PARTIAL-OVERLAP-SAME: 0000000000000130 +# CHECK-PARTIAL-OVERLAP: .text2 +# CHECK-PARTIAL-OVERLAP-SAME: 0000000000000230 +# CHECK-PARTIAL-OVERLAP: .anotherone +# CHECK-PARTIAL-OVERLAP-SAME: 0000000000000320 + +## Check overflow by 1. +# RUN: not llvm-objcopy --change-section-address .anotherone+0xfffffffffffffd00 %ti1 2>&1 | FileCheck %s --check-prefix=ERR-OVERFLOW +## Check underflow by 1. +# RUN: not llvm-objcopy --change-section-address .text2-0x201 %ti1 2>&1 | FileCheck %s --check-prefix=ERR-UNDERFLOW +## Check error when argument value is invalid as a whole. +# RUN: not llvm-objcopy --change-section-address 0 %ti1 2>&1 | FileCheck %s --check-prefix=ERR-IVALID-VAL +## Check error when the value is invalid in the argument value. +# RUN: not llvm-objcopy --change-section-address .anotherone+0c50 %ti1 2>&1 | FileCheck %s --check-prefix=ERR-NOT-INTEGER +## Check error when the value does not fit in uint64_t. +# RUN not llvm-objcopy --change-section-address .text1=0x10000000000000000 %ti1 %to 2>&1 | FileCheck %s --chack-prefix=ERR-NOT-INTEGER +## Check error when the section pattern is missing. +# RUN: not llvm-objcopy --change-section-address =0x10 %ti1 2>&1 | FileCheck %s --check-prefix=ERR-MISSING-SECTION +## Check error when the negative adjustment value is missing. +# RUN: not llvm-objcopy --change-section-address .text1- %ti1 2>&1 | FileCheck %s --check-prefix=ERR-MISSING-VALUE-MINUS +## Check error when the positive adjustment value is missing. +# RUN: not llvm-objcopy --change-section-address .text1+ %ti1 2>&1 | FileCheck %s --check-prefix=ERR-MISSING-VALUE-PLUS +## Check error when the value to set the address to is missing. +# RUN: not llvm-objcopy --change-section-address .text1= %ti1 2>&1 | FileCheck %s --check-prefix=ERR-MISSING-VALUE-EQUAL +## Check error when the provided regex is invalid. +# RUN: not llvm-objcopy --regex --change-section-address "ab**-0x20" %ti1 2>&1 | FileCheck %s --check-prefix=ERR-MATCHER-FAILURE + +# ERR-OVERFLOW: address 0x300 cannot be increased by 0xfffffffffffffd00. The result would overflow +# ERR-UNDERFLOW: address 0x200 cannot be decreased by 0x201. The result would underflow +# ERR-IVALID-VAL: error: bad format for --change-section-address: argument value 0 is invalid. See --help +# ERR-NOT-INTEGER: error: bad format for --change-section-address: value after + is 0c50 when it should be a 64-bit integer +# ERR-MISSING-SECTION: error: bad format for --change-section-address: missing section pattern to apply address change to +# ERR-MISSING-VALUE-MINUS: error: bad format for --change-section-address: missing value of offset after '-' +# ERR-MISSING-VALUE-PLUS: error: bad format for --change-section-address: missing value of offset after '+' +# ERR-MISSING-VALUE-EQUAL: error: bad format for --change-section-address: missing address value after '=' +# ERR-MATCHER-FAILURE: error: cannot compile regular expression 'ab**': repetition-operator operand invalid + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_[[TYPE]] +Sections: + - Name: .text1 + Type: SHT_PROGBITS + Size: 0x100 + Address: 0x100 + - Name: .text2 + Type: SHT_PROGBITS + Size: 0x100 + Address: 0x200 + - Name: .anotherone + Type: SHT_PROGBITS + Size: 0x100 + Address: 0x300 + - Name: =a-b+c++d + Type: SHT_PROGBITS + Size: 0x100 + Address: 0x400 + +# RUN: yaml2obj -DTYPE=EXEC %s -o %ti2 + +## Input file is not ET_REL +# RUN: not llvm-objcopy --change-section-address *+0x20 %ti2 2>&1 | FileCheck %s --check-prefix=ERR-FILE-TYPE + +# ERR-FILE-TYPE: cannot change section address in a non-relocatable file diff --git a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp index a54feb7644bda..d82ecc8b3d36e 100644 --- a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp +++ b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp @@ -584,6 +584,68 @@ static Expected parseChangeSectionLMA(StringRef ArgValue, return *LMAValue; } +static Expected +parseChangeSectionAddr(StringRef ArgValue, StringRef OptionName, + MatchStyle SectionMatchStyle, + function_ref ErrorCallback) { + SectionPatternAddressUpdate PatternUpdate; + + size_t LastSymbolIndex = ArgValue.find_last_of("+-="); + if (LastSymbolIndex == StringRef::npos) + return createStringError(errc::invalid_argument, + "bad format for " + OptionName + + ": argument value " + ArgValue + + " is invalid. See --help"); + char UpdateSymbol = ArgValue[LastSymbolIndex]; + + StringRef SectionPattern = ArgValue.slice(0, LastSymbolIndex); + if (SectionPattern.empty()) + return createStringError( + errc::invalid_argument, + "bad format for " + OptionName + + ": missing section pattern to apply address change to"); + if (Error E = PatternUpdate.SectionPattern.addMatcher(NameOrPattern::create( + SectionPattern, SectionMatchStyle, ErrorCallback))) + return std::move(E); + + StringRef Value = ArgValue.slice(LastSymbolIndex + 1, StringRef::npos); + if (Value.empty()) { + switch (UpdateSymbol) { + case '+': + case '-': + return createStringError(errc::invalid_argument, + "bad format for " + OptionName + + ": missing value of offset after '" + + std::string({UpdateSymbol}) + "'"); + + case '=': + return createStringError(errc::invalid_argument, + "bad format for " + OptionName + + ": missing address value after '='"); + } + } + auto AddrValue = getAsInteger(Value); + if (!AddrValue) + return createStringError(AddrValue.getError(), + "bad format for " + OptionName + ": value after " + + std::string({UpdateSymbol}) + " is " + Value + + " when it should be a 64-bit integer"); + + switch (UpdateSymbol) { + case '+': + PatternUpdate.Update.Kind = AdjustKind::Add; + break; + case '-': + PatternUpdate.Update.Kind = AdjustKind::Subtract; + break; + case '=': + PatternUpdate.Update.Kind = AdjustKind::Set; + } + + PatternUpdate.Update.Value = *AddrValue; + return PatternUpdate; +} + // parseObjcopyOptions returns the config and sets the input arguments. If a // help flag is set then parseObjcopyOptions will print the help messege and // exit. @@ -874,6 +936,15 @@ objcopy::parseObjcopyOptions(ArrayRef RawArgsArr, Config.ChangeSectionLMAValAll = *LMAValue; } + for (auto *Arg : InputArgs.filtered(OBJCOPY_change_section_address)) { + Expected AddressUpdate = + parseChangeSectionAddr(Arg->getValue(), Arg->getSpelling(), + SectionMatchStyle, ErrorCallback); + if (!AddressUpdate) + return AddressUpdate.takeError(); + Config.ChangeSectionAddress.push_back(*AddressUpdate); + } + for (auto *Arg : InputArgs.filtered(OBJCOPY_redefine_symbol)) { if (!StringRef(Arg->getValue()).contains('=')) return createStringError(errc::invalid_argument, diff --git a/llvm/tools/llvm-objcopy/ObjcopyOpts.td b/llvm/tools/llvm-objcopy/ObjcopyOpts.td index b26c497ca3997..434b5ff92324e 100644 --- a/llvm/tools/llvm-objcopy/ObjcopyOpts.td +++ b/llvm/tools/llvm-objcopy/ObjcopyOpts.td @@ -267,6 +267,13 @@ defm change_section_lma : Eq<"change-section-lma", "Shift LMA of non-zero-sized sections in the program header by ">, MetaVarName<"*{+|-}val">; +defm change_section_address + : Eq<"change-section-address", "Set the address of the
to, or adjust it by, ">, + MetaVarName<"sectionpattern{=|+|-}val">; +def adjust_section_vma : JoinedOrSeparate<["--"], "adjust-section-vma">, + Alias, + HelpText<"Alias for --change-section-address">; + defm add_symbol : Eq<"add-symbol", "Add new symbol to .symtab. Accepted flags: " "global, local, weak, default, hidden, protected, file, section, object, "