llvm / llvm/llvm-project

llvm-objcopy rejects OS-specific reserved `st_shndx` such as Linux `SHN_LIVEPATCH` (`0xff20`)

Open
#196,381 5 comments 0 reactions 0 assignees View on GitHub
tools:llvm-objcopy/strip
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Behavior difference

I have an ELF object containing a symbol whose `st_shndx` is `0xff20`.

On Linux, `0xff20` is not an arbitrary value: it is `SHN_LIVEPATCH`, used by the kernel livepatch module ELF format for certain `.klp.sym.*` symbols.

`readelf -sW` shows the symbol as an OS-specific section index:

FUNC GLOBAL DEFAULT OS [0xff20] foo

GNU objcopy accepts the object and can perform a simple transformation:

objcopy --add-section .note.test=empty.bin repro.osff20 repro.gnu.addsec

llvm-objcopy rejects the same input:

llvm-objcopy --add-section .note.test=empty.bin repro.osff20 repro.llvm.addsec

with:

llvm-objcopy: error: symbol 'foo' has unsupported value greater than or equal to SHN_LORESERVE: 65312

65312 == 0xff20.

## Code blocks:

The failure appears to come from llvm/lib/ObjCopy/ELF/ELFObject.cpp.

`isValidReservedSectionIndex()` uses a small whitelist:

```c
static bool isValidReservedSectionIndex(uint16_t Index, uint16_t Machine) {
switch (Index) {
case SHN_ABS:
case SHN_COMMON:
return true;
}

if (Machine == EM_AMDGPU)
return Index == SHN_AMDGPU_LDS;

if (Machine == EM_MIPS) {
switch (Index) {
case SHN_MIPS_ACOMMON:
case SHN_MIPS_SCOMMON:
case SHN_MIPS_SUNDEFINED:
return true;
}
}

if (Machine == EM_HEXAGON) {
switch (Index) {
case SHN_HEXAGON_SCOMMON:
case SHN_HEXAGON_SCOMMON_1:
case SHN_HEXAGON_SCOMMON_2:
case SHN_HEXAGON_SCOMMON_4:
case SHN_HEXAGON_SCOMMON_8:
return true;
}
}

return false;
}
```

Then symbol-table loading hard-fails for any other value greater than or equal to SHN_LORESERVE:

```
} else if (Sym.st_shndx >= SHN_LORESERVE) {
if (!isValidReservedSectionIndex(Sym.st_shndx, Obj.Machine)) {
return createStringError(
errc::invalid_argument,
"symbol '" + *Name +
"' has unsupported value greater than or equal "
"to SHN_LORESERVE: " +
Twine(Sym.st_shndx));
}
}
```

Therefore:
```
Sym.st_shndx = 0xff20
0xff20 >= SHN_LORESERVE
0xff20 is not in the whitelist
```

=> llvm-objcopy rejects the object

## ELF expectation

My understanding is that st_shndx >= SHN_LORESERVE and SHN_LOOS..SHN_HIOS is reserved for OS-specific semantics.

Linux uses one of those values:

```
#define SHN_LIVEPATCH 0xff20
```

This is used by Linux livepatch modules and documented in the kernel livepatch module ELF format.

So this is not a synthetic invalid section index. The minimal reproducer only constructs the smallest object that carries the same real-world ABI condition: an OS-specific reserved symbol section index used by Linux livepatch.

## Questions

Is llvm-objcopy intentionally rejecting all reserved st_shndx values except for a narrow whitelist?

For an object-copying tool, should OS-specific reserved indexes such as SHN_LOOS..SHN_HIOS be preserved rather than rejected, especially when the transformation does not need to interpret the symbol?

At minimum, should LLVM accept Linux SHN_LIVEPATCH (0xff20)?

Possible fixes could be either:

```
if (Index == 0xff20) // Linux SHN_LIVEPATCH: include/uapi/linux/elf.h:#define SHN_LIVEPATCH 0xff20
return true;
```

or, more generally:

```
if (Index >= SHN_LOOS && Index <= SHN_HIOS) // #define SHN_LOOS 0xFF20 /* OS specific semantics, lo */
return true;
```

The latter seems closer to the ELF idea that this range is available for OS-specific semantics, while the former is a narrower Linux-specific compatibility fix.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.