[llvm] [llvm-objcopy] Support Linux livepatch symbols and relocations (PR #224075)

via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 16 09:49:39 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-binary-utilities

Author: Tianyi Chen (chenty2333)

<details>
<summary>Changes</summary>

Linux livepatch objects use `SHN_LIVEPATCH` symbols and allocated relocations against `.symtab`. llvm-objcopy currently rejects the symbols and assumes allocated relocations refer to `.dynsym`.

Recognize livepatch-specific symbol names or relocation-section names and flags in `ET_REL` objects with System V or GNU/Linux OSABI. Preserve the special index and update relocation references when symbols or sections are removed, while continuing to reject unknown reserved indices.

Fixes #<!-- -->196381.

Assisted-by: OpenAI Codex


---
Full diff: https://github.com/llvm/llvm-project/pull/224075.diff


4 Files Affected:

- (modified) llvm/include/llvm/BinaryFormat/ELF.h (+4) 
- (modified) llvm/lib/ObjCopy/ELF/ELFObject.cpp (+36-3) 
- (modified) llvm/lib/ObjCopy/ELF/ELFObject.h (+7-3) 
- (added) llvm/test/tools/llvm-objcopy/ELF/linux-livepatch.test (+135) 


``````````diff
diff --git a/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h
index 6bb387799c4a1..9aca7ac06c8e0 100644
--- a/llvm/include/llvm/BinaryFormat/ELF.h
+++ b/llvm/include/llvm/BinaryFormat/ELF.h
@@ -1144,6 +1144,7 @@ enum {
   SHN_LOPROC = 0xff00,    // Lowest processor-specific index
   SHN_HIPROC = 0xff1f,    // Highest processor-specific index
   SHN_LOOS = 0xff20,      // Lowest operating system-specific index
+  SHN_LIVEPATCH = 0xff20, // Linux livepatch symbol
   SHN_HIOS = 0xff3f,      // Highest operating system-specific index
   SHN_ABS = 0xfff1,       // Symbol has absolute value; does not need relocation
   SHN_COMMON = 0xfff2,    // FORTRAN COMMON or C external global variables
@@ -1296,6 +1297,9 @@ enum : unsigned {
 
   SHF_MASKOS = 0x0ff00000,
 
+  // Linux livepatch relocation section.
+  SHF_RELA_LIVEPATCH = 0x00100000,
+
   // Solaris equivalent of SHF_GNU_RETAIN.
   SHF_SUNW_NODISCARD = 0x00100000,
 
diff --git a/llvm/lib/ObjCopy/ELF/ELFObject.cpp b/llvm/lib/ObjCopy/ELF/ELFObject.cpp
index ac818343f3f8f..819921f293e8f 100644
--- a/llvm/lib/ObjCopy/ELF/ELFObject.cpp
+++ b/llvm/lib/ObjCopy/ELF/ELFObject.cpp
@@ -1563,6 +1563,16 @@ Error ELFBuilder<ELFT>::initGroupSection(GroupSection *GroupSec) {
   return Error::success();
 }
 
+static bool isLinuxRelocatable(uint16_t Type, uint8_t OSABI) {
+  return Type == ET_REL && (OSABI == ELFOSABI_NONE || OSABI == ELFOSABI_GNU);
+}
+
+static bool isLivepatchRelocation(uint32_t Type, uint64_t Flags,
+                                  StringRef Name) {
+  return Type == SHT_RELA && (Flags & SHF_RELA_LIVEPATCH) &&
+         Name.starts_with(".klp.rela.");
+}
+
 template <class ELFT>
 Error ELFBuilder<ELFT>::initSymbolTable(SymbolTableSection *SymTab) {
   Expected<const Elf_Shdr *> Shdr = ElfFile.getSection(SymTab->Index);
@@ -1580,6 +1590,14 @@ Error ELFBuilder<ELFT>::initSymbolTable(SymbolTableSection *SymTab) {
   if (!Symbols)
     return Symbols.takeError();
 
+  // Livepatch generators may also mark auxiliary symbols with SHN_LIVEPATCH,
+  // e.g. objtool's .klp.tombstone.* symbols.
+  bool HasLivepatchRelocations =
+      llvm::any_of(Obj.sections(), [&](const auto &S) {
+        return S.Link == SymTab->Index &&
+               isLivepatchRelocation(S.Type, S.Flags, S.Name);
+      });
+
   for (const typename ELFFile<ELFT>::Elf_Sym &Sym : *Symbols) {
     SectionBase *DefSection = nullptr;
 
@@ -1620,7 +1638,12 @@ Error ELFBuilder<ELFT>::initSymbolTable(SymbolTableSection *SymTab) {
 
       DefSection = *Sec;
     } else if (Sym.st_shndx >= SHN_LORESERVE) {
-      if (!isValidReservedSectionIndex(Sym.st_shndx, Obj.Machine)) {
+      bool IsLivepatch =
+          Sym.st_shndx == SHN_LIVEPATCH &&
+          isLinuxRelocatable(Obj.Type, Obj.OSABI) &&
+          (Name->starts_with(".klp.sym.") || HasLivepatchRelocations);
+      if (!IsLivepatch &&
+          !isValidReservedSectionIndex(Sym.st_shndx, Obj.Machine)) {
         return createStringError(
             errc::invalid_argument,
             "symbol '" + *Name +
@@ -1708,14 +1731,24 @@ Expected<SectionBase &> ELFBuilder<ELFT>::makeSection(const Elf_Shdr &Shdr) {
   switch (Shdr.sh_type) {
   case SHT_REL:
   case SHT_RELA:
-  case SHT_CREL:
-    if (Shdr.sh_flags & SHF_ALLOC) {
+  case SHT_CREL: {
+    Expected<StringRef> Name = ElfFile.getSectionName(Shdr);
+    if (!Name)
+      return Name.takeError();
+    const auto &Ehdr = ElfFile.getHeader();
+    // Livepatch relocations are allocated but refer to the regular symbol
+    // table, so their symbol references must be updated when it changes.
+    bool IsLivepatch =
+        isLinuxRelocatable(Ehdr.e_type, Ehdr.e_ident[EI_OSABI]) &&
+        isLivepatchRelocation(Shdr.sh_type, Shdr.sh_flags, *Name);
+    if ((Shdr.sh_flags & SHF_ALLOC) && !IsLivepatch) {
       if (Expected<ArrayRef<uint8_t>> Data = ElfFile.getSectionContents(Shdr))
         return Obj.addSection<DynamicRelocationSection>(*Data);
       else
         return Data.takeError();
     }
     return Obj.addSection<RelocationSection>(Obj);
+  }
   case SHT_STRTAB:
     // If a string table is allocated we don't want to mess with it. That would
     // mean altering the memory image. There are no special link types or
diff --git a/llvm/lib/ObjCopy/ELF/ELFObject.h b/llvm/lib/ObjCopy/ELF/ELFObject.h
index 2783ef27ac9de..34a85e204ab31 100644
--- a/llvm/lib/ObjCopy/ELF/ELFObject.h
+++ b/llvm/lib/ObjCopy/ELF/ELFObject.h
@@ -530,6 +530,8 @@ class SectionBase {
   uint64_t Type = ELF::SHT_NULL;
   ArrayRef<uint8_t> OriginalData;
   bool HasSymbol = false;
+  // SHF_ALLOC does not distinguish dynamic from Linux livepatch relocations.
+  bool IsDynamicRelocation = false;
 
   SectionBase() = default;
   SectionBase(const SectionBase &) = default;
@@ -928,7 +930,7 @@ class RelocationSection
   const Object &getObject() const { return Obj; }
 
   static bool classof(const SectionBase *S) {
-    if (S->OriginalFlags & ELF::SHF_ALLOC)
+    if (S->IsDynamicRelocation)
       return false;
     return RelocationSectionBase::classof(S);
   }
@@ -1006,7 +1008,9 @@ class DynamicRelocationSection
   ArrayRef<uint8_t> Contents;
 
 public:
-  explicit DynamicRelocationSection(ArrayRef<uint8_t> Data) : Contents(Data) {}
+  explicit DynamicRelocationSection(ArrayRef<uint8_t> Data) : Contents(Data) {
+    IsDynamicRelocation = true;
+  }
 
   Error accept(SectionVisitor &) const override;
   Error accept(MutableSectionVisitor &Visitor) override;
@@ -1015,7 +1019,7 @@ class DynamicRelocationSection
       function_ref<bool(const SectionBase *)> ToRemove) override;
 
   static bool classof(const SectionBase *S) {
-    if (!(S->OriginalFlags & ELF::SHF_ALLOC))
+    if (!S->IsDynamicRelocation)
       return false;
     return S->OriginalType == ELF::SHT_REL || S->OriginalType == ELF::SHT_RELA;
   }
diff --git a/llvm/test/tools/llvm-objcopy/ELF/linux-livepatch.test b/llvm/test/tools/llvm-objcopy/ELF/linux-livepatch.test
new file mode 100644
index 0000000000000..dc49f566fe444
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/ELF/linux-livepatch.test
@@ -0,0 +1,135 @@
+## Linux livepatch relocations are allocated but reference .symtab, not .dynsym.
+## Preserve their names, flags and symbol references when copying or stripping.
+# RUN: yaml2obj %s -o %t
+# RUN: llvm-objcopy %t %t.copy
+# RUN: llvm-readobj --sections --symbols --relocations %t.copy | FileCheck %s
+# RUN: echo added > %t.data
+# RUN: llvm-objcopy --add-section .extra=%t.data %t %t.add
+# RUN: llvm-readobj --sections --symbols --relocations %t.add | FileCheck %s --check-prefixes=CHECK,ADD
+
+## Removing debug sections and an unreferenced symbol changes section and
+## symbol indices. Both kinds of relocation must still have valid references.
+# RUN: llvm-objcopy --strip-unneeded %t %t.strip
+# RUN: llvm-readobj --sections --symbols --relocations %t.strip | FileCheck %s --implicit-check-not=unused
+# RUN: llvm-strip --strip-unneeded %t -o %t.strip
+# RUN: llvm-readobj --sections --symbols --relocations %t.strip | FileCheck %s --implicit-check-not=unused
+# RUN: not llvm-objcopy --strip-symbol='.klp.sym.vmlinux.foo,0' %t %t.bad 2>&1 | FileCheck %s --check-prefix=REFERENCED
+# REFERENCED: not stripping symbol '.klp.sym.vmlinux.foo,0' because it is named in a relocation
+
+## GNU/Linux OSABI is also accepted, although Linux modules normally use NONE.
+# RUN: yaml2obj %s -DABI=ELFOSABI_GNU -o %t.gnu
+# RUN: llvm-objcopy %t.gnu %t.copy
+# RUN: llvm-readobj --sections --symbols --relocations %t.copy | FileCheck %s
+
+# CHECK:      Name: .text
+# CHECK:      Name: .rela.text
+# CHECK:      Index: [[RELOC:[0-9]+]]
+# CHECK-NEXT: Name: .klp.rela.vmlinux..text
+# CHECK-NEXT: Type: SHT_RELA
+# CHECK-NEXT: Flags [ (0x100042)
+# CHECK:      Link: [[SYMTAB:[0-9]+]]
+# CHECK-NEXT: Info: 1
+# CHECK:      Index: [[SYMTAB]]
+# CHECK-NEXT: Name: .symtab
+# ADD:        Name: .extra
+# CHECK:      Relocations [
+# CHECK:      Section ({{[0-9]+}}) .rela.text {
+# CHECK-NEXT:   0x0 R_X86_64_NONE .klp.tombstone.foo 0x0
+# CHECK-NEXT:   0x8 R_X86_64_64 regular 0x0
+# CHECK-NEXT: }
+# CHECK-NEXT: Section ([[RELOC]]) .klp.rela.vmlinux..text {
+# CHECK-NEXT:   0x0 R_X86_64_64 .klp.sym.vmlinux.foo,0 0x7
+# CHECK-NEXT: }
+# CHECK:      Name: .klp.sym.vmlinux.foo,0
+# CHECK-NEXT: Value: 0x0
+# CHECK-NEXT: Size: 0
+# CHECK-NEXT: Binding: Local
+# CHECK-NEXT: Type: Object
+# CHECK-NEXT: Other: 0
+# CHECK-NEXT: Section: {{.*}} (0xFF20)
+# CHECK:      Name: .klp.tombstone.foo
+# CHECK-NEXT: Value: 0x0
+# CHECK-NEXT: Size: 0
+# CHECK-NEXT: Binding: Weak
+# CHECK-NEXT: Type: None
+# CHECK-NEXT: Other: 0
+# CHECK-NEXT: Section: {{.*}} (0xFF20)
+
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_REL
+  Machine: EM_X86_64
+  OSABI:   [[ABI=ELFOSABI_NONE]]
+Sections:
+  - Name:    .text
+    Type:    SHT_PROGBITS
+    Flags:   [ SHF_ALLOC, SHF_EXECINSTR ]
+    Content: '00000000000000000000000000000000'
+  - Name: .debug_info
+    Type: SHT_PROGBITS
+  - Name: .rela.text
+    Type: SHT_RELA
+    Link: .symtab
+    Info: .text
+    Relocations:
+      - Offset: 0
+        Symbol: .klp.tombstone.foo
+        Type:   R_X86_64_NONE
+      - Offset: 8
+        Symbol: regular
+        Type:   R_X86_64_64
+  - Name:    .klp.rela.vmlinux..text
+    Type:    SHT_RELA
+    ShFlags: 0x100042 # SHF_ALLOC | SHF_INFO_LINK | SHF_RELA_LIVEPATCH
+    Link:    .symtab
+    Info:    .text
+    Relocations:
+      - Offset: 0
+        Symbol: .klp.sym.vmlinux.foo,0
+        Type:   R_X86_64_64
+        Addend: 7
+Symbols:
+  - Name:  unused
+    Index: SHN_ABS
+  - Name:  .klp.sym.vmlinux.foo,0
+    Type:  STT_OBJECT
+    Index: 0xff20 # SHN_LIVEPATCH
+  # objtool also marks the symbols in disabled ordinary relocations.
+  - Name:    .klp.tombstone.foo
+    Binding: STB_WEAK
+    Index:   0xff20
+  - Name:    regular
+    Binding: STB_GLOBAL
+    Section: .text
+
+## A livepatch symbol can also be recognized without a relocation section.
+# RUN: yaml2obj --docnum=2 %s -o %t.sym
+# RUN: llvm-objcopy %t.sym %t.copy
+# RUN: llvm-readobj --symbols %t.copy | FileCheck %s --check-prefix=SYMBOL
+# SYMBOL:      Name: .klp.sym.vmlinux.foo,0
+# SYMBOL:      Section: {{.*}} (0xFF20)
+
+## Do not interpret an arbitrary OS-specific index as Linux livepatch, even
+## with a livepatch symbol name, or accept an unrecognized use of 0xff20.
+# RUN: yaml2obj --docnum=2 %s -DABI=ELFOSABI_FREEBSD -o %t.bad
+# RUN: not llvm-objcopy %t.bad %t.copy 2>&1 | FileCheck %s --check-prefix=INDEX-ERR -DINDEX=65312
+# RUN: yaml2obj --docnum=2 %s -DTYPE=ET_EXEC -o %t.bad
+# RUN: not llvm-objcopy %t.bad %t.copy 2>&1 | FileCheck %s --check-prefix=INDEX-ERR -DINDEX=65312
+# RUN: yaml2obj --docnum=2 %s -DINDEX=0xff21 -o %t.bad
+# RUN: not llvm-objcopy %t.bad %t.copy 2>&1 | FileCheck %s --check-prefix=INDEX-ERR -DINDEX=65313
+# RUN: yaml2obj --docnum=2 %s -DNAME=foo -o %t.bad
+# RUN: not llvm-objcopy %t.bad %t.copy 2>&1 | FileCheck %s --check-prefix=INDEX-ERR -DINDEX=65312
+# INDEX-ERR: symbol '{{.*}}' has unsupported value greater than or equal to SHN_LORESERVE: [[INDEX]]
+
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    [[TYPE=ET_REL]]
+  Machine: EM_X86_64
+  OSABI:   [[ABI=ELFOSABI_NONE]]
+Symbols:
+  - Name:  [[NAME=.klp.sym.vmlinux.foo,0]]
+    Index: [[INDEX=0xff20]]

``````````

</details>


https://github.com/llvm/llvm-project/pull/224075


More information about the llvm-commits mailing list