Index: test/tools/llvm-objcopy/ELF/add-symbol.test =================================================================== --- test/tools/llvm-objcopy/ELF/add-symbol.test +++ test/tools/llvm-objcopy/ELF/add-symbol.test @@ -0,0 +1,76 @@ +# RUN: yaml2obj %s -o %t +# RUN: llvm-objcopy --add-symbol='abs1=1' \ +# RUN: --add-symbol='abs2=2,LoCaL,HiDdEn,FuNcTiOn' \ +# RUN: --add-symbol='abs3=3,global,default,object' \ +# RUN: --add-symbol='foo.cpp=0,file' \ +# RUN: --add-symbol='=.text:0,section' \ +# RUN: --add-symbol='data=.data:0x100,weak' \ +# RUN: --add-symbol='ifunc=.text:0,indirect-function' \ +# RUN: %t %t2 +# RUN: llvm-readelf -s %t2 | FileCheck %s + +# Checked ignored options +# RUN: llvm-objcopy \ +# RUN: --add-symbol='dummy1=0,indirect,constructor,debug,synthetic' \ +# RUN: --add-symbol='dummy2=0,before=foo,unique-object,warning' %t %t3 +# RUN: llvm-readelf -s %t3 | FileCheck %s --check-prefix=IGNORED + +# Check errors +# RUN: not llvm-objcopy --add-symbol='test' %t %t4 2>&1 | FileCheck %s --check-prefix=ERR1 +# RUN: not llvm-objcopy --add-symbol='test=:0' %t %t5 2>&1 | FileCheck %s --check-prefix=ERR2 +# RUN: not llvm-objcopy --add-symbol='test=foo:' %t %t6 2>&1 | FileCheck %s --check-prefix=ERR2 +# RUN: not llvm-objcopy --add-symbol='test=0,cool' %t %t7 2>&1 | FileCheck %s --check-prefix=ERR3 +# RUN: not llvm-objcopy --add-symbol='test=xyz' %t %t8 2>&1 | FileCheck %s --check-prefix=ERR4 + +!ELF +FileHeader: + Class: ELFCLASS32 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_ARM +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x0000000000001000 + Size: 64 + - Name: .data + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC ] + Address: 0x2000 + AddressAlign: 0x0000000000001000 + Size: 64 +ProgramHeaders: + - Type: PT_LOAD + Flags: [ PF_X, PF_R ] + VAddr: 0x1000 + PAddr: 0x1000 + Align: 0x1000 + Sections: + - Section: .text + - Type: PT_LOAD + Flags: [ PF_R, PF_W ] + VAddr: 0x2000 + PAddr: 0x2000 + Align: 0x1000 + Sections: + - Section: .data + +# CHECK: 0: 00000000 0 NOTYPE LOCAL DEFAULT UND +# CHECK-NEXT: 1: 00000001 0 NOTYPE GLOBAL DEFAULT ABS abs1 +# CHECK-NEXT: 2: 00000002 0 FUNC LOCAL HIDDEN ABS abs2 +# CHECK-NEXT: 3: 00000003 0 OBJECT GLOBAL DEFAULT ABS abs3 +# CHECK-NEXT: 4: 00000000 0 FILE GLOBAL DEFAULT ABS foo.cpp +# CHECK-NEXT: 5: 00001000 0 SECTION GLOBAL DEFAULT 1 +# CHECK-NEXT: 6: 00002100 0 NOTYPE WEAK DEFAULT 2 data +# CHECK-NEXT: 7: 00001000 0 IFUNC GLOBAL DEFAULT 1 ifunc + +# IGNORED: 1: 00000000 0 NOTYPE GLOBAL DEFAULT ABS dummy1 +# IGNORED-NEXT: 2: 00000000 0 NOTYPE GLOBAL DEFAULT ABS dummy2 + +# ERR1: error: bad format for --add-symbol, missing '=' after 'test' +# ERR2: error: bad format for --add-symbol, missing section name or symbol value +# ERR3: error: unsupported flag 'cool' for --add-symbol +# ERR4: error: bad symbol value: 'xyz' + Index: tools/llvm-objcopy/COFF/COFFObjcopy.cpp =================================================================== --- tools/llvm-objcopy/COFF/COFFObjcopy.cpp +++ tools/llvm-objcopy/COFF/COFFObjcopy.cpp @@ -185,7 +185,8 @@ Config.ExtractDWO || Config.KeepFileSymbols || Config.LocalizeHidden || Config.PreserveDates || Config.StripDWO || Config.StripNonAlloc || Config.StripSections || Config.Weaken || Config.DecompressDebugSections || - Config.DiscardMode == DiscardType::Locals) { + Config.DiscardMode == DiscardType::Locals || + !Config.SymbolsToAdd.empty()) { return createStringError(llvm::errc::invalid_argument, "Option not supported by llvm-objcopy for COFF"); } Index: tools/llvm-objcopy/CopyConfig.h =================================================================== --- tools/llvm-objcopy/CopyConfig.h +++ tools/llvm-objcopy/CopyConfig.h @@ -14,6 +14,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Object/ELFTypes.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/Error.h" #include "llvm/Support/Regex.h" @@ -61,6 +62,15 @@ bool operator!=(StringRef S) const { return !operator==(S); } }; +struct NewSymbolInfo { + StringRef SymbolName; + StringRef SectionName; + unsigned long long Value = 0; + uint8_t Type = ELF::STT_NOTYPE; + uint8_t Bind = ELF::STB_GLOBAL; + uint8_t Visibility = ELF::STV_DEFAULT; +}; + // Configuration for copying/stripping a single file. struct CopyConfig { // Main input/output options @@ -86,6 +96,7 @@ // Repeated options std::vector AddSection; std::vector DumpSection; + std::vector SymbolsToAdd; std::vector KeepSection; std::vector OnlySection; std::vector SymbolsToGlobalize; Index: tools/llvm-objcopy/CopyConfig.cpp =================================================================== --- tools/llvm-objcopy/CopyConfig.cpp +++ tools/llvm-objcopy/CopyConfig.cpp @@ -13,7 +13,6 @@ #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" -#include "llvm/Object/ELFTypes.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Support/CommandLine.h" @@ -206,6 +205,169 @@ return SFU; } +static const char *getBindAsString(uint8_t Value) { + switch (Value) { + case ELF::STB_GLOBAL: + return "global"; + case ELF::STB_LOCAL: + return "local"; + case ELF::STB_WEAK: + return "weak"; + default: + llvm_unreachable("unexpected bind value"); + } +} + +static const char *getTypeAsString(uint8_t Value) { + switch (Value) { + case ELF::STT_FILE: + return "file"; + case ELF::STT_SECTION: + return "section"; + case ELF::STT_FUNC: + return "function"; + case ELF::STT_OBJECT: + return "object"; + case ELF::STT_GNU_IFUNC: + return "indirect-function"; + default: + llvm_unreachable("unexpected type value"); + } +} + +static const char *getVisibilityAsString(uint8_t Value) { + switch (Value) { + case ELF::STV_DEFAULT: + return "default"; + case ELF::STV_HIDDEN: + return "hidden"; + default: + llvm_unreachable("unexpected visibility value"); + } +} + +static Error setBindOnce(Optional &Bind, uint8_t Value) { + if (Bind) + return createStringError(errc::invalid_argument, + "conflicting values for binding: %s and %s", + getBindAsString(*Bind), getBindAsString(Value)); + Bind = Value; + return Error::success(); +} + +static Error setTypeOnce(Optional &Type, uint8_t Value) { + if (Type) + return createStringError(errc::invalid_argument, + "conflicting values for type: %s and %s", + getTypeAsString(*Type), getTypeAsString(Value)); + Type = Value; + return Error::success(); +} + +static Error setVisibilityOnce(Optional &Vis, uint8_t Value) { + if (Vis) + return createStringError( + errc::invalid_argument, "conflicting values for visibility: %s and %s", + getVisibilityAsString(*Vis), getVisibilityAsString(Value)); + Vis = Value; + return Error::success(); +} + +static Expected parseNewSymbolInfo(StringRef FlagValue) { + // Parse value given with --add-symbol option and create the + // new symbol if possible. The value format for --add-symbol is: + // + // =[
:][,] + // + // where: + // - symbol name, can be empty string + //
- optional section name. If not given ABS symbol is created + // - symbol value, can be decimal or hexadecimal number prefixed + // with 0x. + // - optional flags affecting symbol type, binding or visibility: + // The following are currently supported: + // + // global, local, weak, default, hidden, file, section, object, + // indirect-function. + // + // The following flags are ignored and provided for GNU + // compatibility only: + // + // warning, debug, constructor, indirect, synthetic, + // unique-object, before=. + NewSymbolInfo SI; + StringRef Value; + std::tie(SI.SymbolName, Value) = FlagValue.split('='); + if (Value.empty()) + return createStringError( + errc::invalid_argument, + "bad format for --add-symbol, missing '=' after '%s'", + SI.SymbolName.str().c_str()); + + if (Value.contains(':')) { + std::tie(SI.SectionName, Value) = Value.split(':'); + if (SI.SectionName.empty() || Value.empty()) + return createStringError( + errc::invalid_argument, + "bad format for --add-symbol, missing section name or symbol value"); + } + + SmallVector Flags; + Value.split(Flags, ','); + if (Flags[0].getAsInteger(0, SI.Value)) + return createStringError(errc::invalid_argument, "bad symbol value: '%s'", + Flags[0].str().c_str()); + + typedef std::function Functor; + Optional Type, Bind, Vis; + size_t NumFlags = Flags.size(); + for (size_t I = 1; I < NumFlags; ++I) { + Functor F = + StringSwitch(Flags[I]) + .CaseLower("global", + [&Bind] { return setBindOnce(Bind, ELF::STB_GLOBAL); }) + .CaseLower("local", + [&Bind] { return setBindOnce(Bind, ELF::STB_LOCAL); }) + .CaseLower("weak", + [&Bind] { return setBindOnce(Bind, ELF::STB_WEAK); }) + .CaseLower( + "default", + [&Vis] { return setVisibilityOnce(Vis, ELF::STV_DEFAULT); }) + .CaseLower( + "hidden", + [&Vis] { return setVisibilityOnce(Vis, ELF::STV_HIDDEN); }) + .CaseLower("file", + [&Type] { return setTypeOnce(Type, ELF::STT_FILE); }) + .CaseLower("section", + [&Type] { return setTypeOnce(Type, ELF::STT_SECTION); }) + .CaseLower("object", + [&Type] { return setTypeOnce(Type, ELF::STT_OBJECT); }) + .CaseLower("function", + [&Type] { return setTypeOnce(Type, ELF::STT_FUNC); }) + .CaseLower( + "indirect-function", + [&Type] { return setTypeOnce(Type, ELF::STT_GNU_IFUNC); }) + .CaseLower("debug", [] { return Error::success(); }) + .CasesLower("constructor", "warning", "indirect", "synthetic", + "unique-object", [] { return Error::success(); }) + .StartsWithLower("before", [] { return Error::success(); }) + .Default([&] { + return createStringError(errc::invalid_argument, + "unsupported flag '%s' for --add-symbol", + Flags[I].str().c_str()); + }); + if (Error E = F()) + return std::move(E); + } + if (Type) + SI.Type = *Type; + if (Bind) + SI.Bind = *Bind; + if (Vis) + SI.Visibility = *Vis; + return SI; +} + static const StringMap ArchMap{ // Name, {EMachine, 64bit, LittleEndian} {"aarch64", {ELF::EM_AARCH64, true, true}}, @@ -539,6 +701,11 @@ return std::move(E); for (auto Arg : InputArgs.filtered(OBJCOPY_keep_symbol)) Config.SymbolsToKeep.emplace_back(Arg->getValue(), UseRegex); + for (auto Arg : InputArgs.filtered(OBJCOPY_add_symbol)) + if (Expected SI = parseNewSymbolInfo(Arg->getValue())) + Config.SymbolsToAdd.push_back(*SI); + else + return SI.takeError(); Config.DeterministicArchives = InputArgs.hasFlag( OBJCOPY_enable_deterministic_archives, Index: tools/llvm-objcopy/ELF/ELFObjcopy.cpp =================================================================== --- tools/llvm-objcopy/ELF/ELFObjcopy.cpp +++ tools/llvm-objcopy/ELF/ELFObjcopy.cpp @@ -566,6 +566,14 @@ if (!Config.AddGnuDebugLink.empty()) Obj.addSection(Config.AddGnuDebugLink); + for (const NewSymbolInfo &SI : Config.SymbolsToAdd) { + SectionBase *Sec = Obj.findSection(SI.SectionName); + uint64_t Value = Sec ? Sec->Addr + SI.Value : SI.Value; + Obj.SymbolTable->addSymbol(SI.SymbolName, SI.Bind, SI.Type, Sec, Value, + SI.Visibility, + Sec ? SYMBOL_SIMPLE_INDEX : SHN_ABS, 0); + } + return Error::success(); } Index: tools/llvm-objcopy/ELF/Object.h =================================================================== --- tools/llvm-objcopy/ELF/Object.h +++ tools/llvm-objcopy/ELF/Object.h @@ -805,6 +805,11 @@ ConstRange sections() const { return make_pointee_range(Sections); } + SectionBase *findSection(StringRef Name) { + auto SecIt = + find_if(Sections, [&](const SecPtr &Sec) { return Sec->Name == Name; }); + return SecIt == Sections.end() ? nullptr : SecIt->get(); + } Range segments() { return make_pointee_range(Segments); } ConstRange segments() const { return make_pointee_range(Segments); } Index: tools/llvm-objcopy/ObjcopyOpts.td =================================================================== --- tools/llvm-objcopy/ObjcopyOpts.td +++ tools/llvm-objcopy/ObjcopyOpts.td @@ -237,3 +237,7 @@ def regex : Flag<["-", "--"], "regex">, HelpText<"Permit regular expressions in name comparison">; + +defm add_symbol + : Eq<"add-symbol", "Add new symbol to .symtab.">, + MetaVarName<"name=[section:]value[,flags]">;