[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