Skip to content

Commit

Permalink
Better convention and exception message
Browse files Browse the repository at this point in the history
  • Loading branch information
uranusjr committed Aug 20, 2022
1 parent 37b167d commit 3f51b5f
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 27 deletions.
33 changes: 21 additions & 12 deletions packaging/_elffile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""ELF file parser.
"""
ELF file parser.
This provides a class ``ElfFile`` that parses an ELF executable in a similar
This provides a class ``ELFFile`` that parses an ELF executable in a similar
interface to ``ZipFile``. Only the read interface is implemented.
Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
Expand All @@ -13,7 +14,7 @@
from typing import IO, Optional, Tuple


class ElfInvalid(ValueError):
class ELFInvalid(ValueError):
pass


Expand All @@ -35,18 +36,21 @@ class EMachine(enum.IntEnum):
AArc64 = 183


class ElfFile:
"""Representation of an ELF executable."""
class ELFFile:
"""
Representation of an ELF executable.
"""

def __init__(self, f: IO[bytes]) -> None:
self._f = f

try:
ident = self._read("16B")
except struct.error:
raise ElfInvalid
if bytes(ident[:4]) != b"\x7fELF": # Invalid magic, not ELF.
raise ElfInvalid
raise ELFInvalid("unable to parse identification")
magic = bytes(ident[:4])
if magic != b"\x7fELF":
raise ELFInvalid(f"invalid magic: {magic!r}")

self.capacity = ident[4] # Format for program header (bitness).
self.encoding = ident[5] # Data structure encoding (endianess).
Expand All @@ -62,7 +66,10 @@ def __init__(self, f: IO[bytes]) -> None:
(2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB.
}[(self.capacity, self.encoding)]
except KeyError:
raise ElfInvalid
raise ELFInvalid(
f"unrecognized capacity ({self.capacity}) or "
f"encoding ({self.encoding})"
)

try:
(
Expand All @@ -77,15 +84,17 @@ def __init__(self, f: IO[bytes]) -> None:
self._e_phentsize, # Size of section.
self._e_phnum, # Number of sections.
) = self._read(e_fmt)
except struct.error:
raise ElfInvalid
except struct.error as e:
raise ELFInvalid("unable to parse machine and section information") from e

def _read(self, fmt: str) -> Tuple[int, ...]:
return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))

@property
def interpreter(self) -> Optional[str]:
"""Path recorded in the ``PT_INTERP`` section header."""
"""
The path recorded in the ``PT_INTERP`` section header.
"""
for index in range(self._e_phnum):
self._f.seek(self._e_phoff + self._e_phentsize * index)
try:
Expand Down
6 changes: 3 additions & 3 deletions packaging/_manylinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
import warnings
from typing import Dict, Generator, Iterator, NamedTuple, Optional, Tuple

from ._elffile import EIClass, EIData, ElfFile, EMachine
from ._elffile import EIClass, EIData, ELFFile, EMachine

EF_ARM_ABIMASK = 0xFF000000
EF_ARM_ABI_VER5 = 0x05000000
EF_ARM_ABI_FLOAT_HARD = 0x00000400


@contextlib.contextmanager
def _parse_elf(path: str) -> Generator[Optional[ElfFile], None, None]:
def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]:
try:
with open(path, "rb") as f:
yield ElfFile(f)
yield ELFFile(f)
except (OSError, TypeError, ValueError):
yield None

Expand Down
4 changes: 2 additions & 2 deletions packaging/_musllinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import sys
from typing import Iterator, NamedTuple, Optional

from ._elffile import ElfFile
from ._elffile import ELFFile


class _MuslVersion(NamedTuple):
Expand Down Expand Up @@ -42,7 +42,7 @@ def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
"""
try:
with open(executable, "rb") as f:
ld = ElfFile(f).interpreter
ld = ELFFile(f).interpreter
except (OSError, TypeError, ValueError):
return None
if ld is None or "musl" not in ld:
Expand Down
20 changes: 10 additions & 10 deletions tests/test_elffile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from packaging._elffile import EIClass, EIData, ElfFile, ElfInvalid, EMachine
from packaging._elffile import EIClass, EIData, ELFFile, ELFInvalid, EMachine

DIR_MANYLINUX = pathlib.Path(__file__, "..", "manylinux").resolve()
DIR_MUSLLINUX = pathlib.Path(__file__, "..", "musllinux").resolve()
Expand All @@ -25,7 +25,7 @@
def test_elffile_glibc(name, capacity, encoding, machine):
path = DIR_MANYLINUX.joinpath(f"hello-world-{name}")
with path.open("rb") as f:
ef = ElfFile(f)
ef = ELFFile(f)
assert ef.capacity == capacity
assert ef.encoding == encoding
assert ef.machine == machine
Expand All @@ -49,7 +49,7 @@ def test_elffile_glibc(name, capacity, encoding, machine):
def test_elffile_musl(name, capacity, encoding, machine, interpreter):
path = DIR_MUSLLINUX.joinpath(f"musl-{name}")
with path.open("rb") as f:
ef = ElfFile(f)
ef = ELFFile(f)
assert ef.capacity == capacity
assert ef.encoding == encoding
assert ef.machine == machine
Expand All @@ -69,25 +69,25 @@ def test_elffile_musl(name, capacity, encoding, machine, interpreter):
ids=["no-magic", "wrong-magic", "unknown-format"],
)
def test_elffile_bad_ident(data):
with pytest.raises(ElfInvalid):
ElfFile(io.BytesIO(data))
with pytest.raises(ELFInvalid):
ELFFile(io.BytesIO(data))


def test_elffile_no_section():
"""Enough for magic, but not the section definitions."""
data = BIN_MUSL_X86_64[:25]
with pytest.raises(ElfInvalid):
ElfFile(io.BytesIO(data))
with pytest.raises(ELFInvalid):
ELFFile(io.BytesIO(data))


def test_elffile_invalid_section():
"""Enough for section definitions, but not the actual sections."""
data = BIN_MUSL_X86_64[:58]
assert ElfFile(io.BytesIO(data)).interpreter is None
assert ELFFile(io.BytesIO(data)).interpreter is None


def test_elffle_no_interpreter_section():
ef = ElfFile(io.BytesIO(BIN_MUSL_X86_64))
ef = ELFFile(io.BytesIO(BIN_MUSL_X86_64))

# Change all sections to *not* PT_INTERP.
data = BIN_MUSL_X86_64
Expand All @@ -97,4 +97,4 @@ def test_elffle_no_interpreter_section():
section = struct.unpack(ef._p_fmt, data[sb:se])
data = data[:sb] + struct.pack(ef._p_fmt, 0, *section[1:]) + data[se:]

assert ElfFile(io.BytesIO(data)).interpreter is None
assert ELFFile(io.BytesIO(data)).interpreter is None

0 comments on commit 3f51b5f

Please sign in to comment.