Skip to content

Commit

Permalink
Address review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
mayeut committed Nov 2, 2019
1 parent 5411316 commit 8f7f708
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 35 deletions.
118 changes: 95 additions & 23 deletions packaging/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,25 @@
import os
import platform
import re
import struct
import sys
import sysconfig
import warnings

from ._typing import MYPY_CHECK_RUNNING, cast

if MYPY_CHECK_RUNNING: # pragma: no cover
from typing import Dict, FrozenSet, Iterable, Iterator, List, Optional, Tuple, Union
from typing import (
Dict,
FrozenSet,
IO,
Iterable,
Iterator,
List,
Optional,
Tuple,
Union,
)

PythonVersion = Tuple[int, int]
MacVersion = Tuple[int, int]
Expand Down Expand Up @@ -420,38 +431,99 @@ def _have_compatible_glibc(required_major, minimum_minor):
return _check_glibc_version(version_str, required_major, minimum_minor)


# Python does not provide platform information at sufficient granularity to
# identify the architecture of the running executable in some cases, so we
# determine it dynamically by reading the information from the running
# process. This only applies on Linux, which uses the ELF format.
class _ELFFileHeader(object):
# https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
class _InvalidELFFileHeader(ValueError):
"""
An invalid ELF file header was found.
"""

ELF_MAGIC_NUMBER = 0x7F454C46
ELFCLASS32 = 1
ELFCLASS64 = 2
ELFDATA2LSB = 1
ELFDATA2MSB = 2
EM_386 = 0x03
EM_ARM = 0x28
EF_ARM_ABIMASK = 0xFF000000
EF_ARM_ABI_VER5 = 0x05000000
EF_ARM_ABI_FLOAT_HARD = 0x00000400

def __init__(self, file):
# type: (_ELFFileHeader, IO[bytes]) -> None
def unpack(fmt):
# type: (str) -> int
try:
result, = struct.unpack(
fmt, file.read(struct.calcsize(fmt))
) # type: (int, )
except struct.error:
raise _ELFFileHeader._InvalidELFFileHeader()
return result

self.e_ident_magic = unpack(">I")
if self.e_ident_magic != self.ELF_MAGIC_NUMBER:
raise _ELFFileHeader._InvalidELFFileHeader()
self.e_ident_class = unpack("B")
if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}:
raise _ELFFileHeader._InvalidELFFileHeader()
self.e_ident_data = unpack("B")
if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}:
raise _ELFFileHeader._InvalidELFFileHeader()
self.e_ident_version = unpack("B")
self.e_ident_osabi = unpack("B")
self.e_ident_abiversion = unpack("B")
self.e_ident_pad = file.read(7)
format_h = "<H" if self.e_ident_data == self.ELFDATA2LSB else ">H"
format_i = "<I" if self.e_ident_data == self.ELFDATA2LSB else ">I"
format_q = "<Q" if self.e_ident_data == self.ELFDATA2LSB else ">Q"
format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q
self.e_type = unpack(format_h)
self.e_machine = unpack(format_h)
self.e_version = unpack(format_i)
self.e_entry = unpack(format_p)
self.e_phoff = unpack(format_p)
self.e_shoff = unpack(format_p)
self.e_flags = unpack(format_i)
self.e_ehsize = unpack(format_h)
self.e_phentsize = unpack(format_h)
self.e_phnum = unpack(format_h)
self.e_shentsize = unpack(format_h)
self.e_shnum = unpack(format_h)
self.e_shstrndx = unpack(format_h)


def _get_elf_header():
# type: () -> Optional[List[int]]
# type: () -> Optional[_ELFFileHeader]
try:
with open(sys.executable, "rb") as f:
elf_header_raw = f.read(40) # read 40 first bytes of ELF header
except (IOError, OSError, TypeError):
return None
if elf_header_raw is None or len(elf_header_raw) < 40:
elf_header = _ELFFileHeader(f)
except (IOError, OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader):
return None
if isinstance(elf_header_raw, str): # pragma: no cover
# python 2.7
elf_header = [ord(c) for c in elf_header_raw]
else: # pragma: no cover
# python 3.x
elf_header = [b for b in elf_header_raw]
if elf_header[0:4] == [0x7F, 0x45, 0x4C, 0x46]: # ELF magic number
return elf_header
return None
return elf_header


def _is_linux_armhf():
# type: () -> bool
# hard-float ABI can be detected from the ELF header of the running
# process
# https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
elf_header = _get_elf_header()
if elf_header is None:
return False
result = elf_header[4:5] == [1] # 32-bit ELF
result &= elf_header[5:6] == [1] # little-endian
result &= elf_header[18:20] == [0x28, 0] # ARM machine
result &= elf_header[39:40] == [5] # ARM EABIv5
result &= (elf_header[37:38][0] & 4) == 4 # EF_ARM_ABI_FLOAT_HARD
result = elf_header.e_ident_class == elf_header.ELFCLASS32
result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB
result &= elf_header.e_machine == elf_header.EM_ARM
result &= (
elf_header.e_flags & elf_header.EF_ARM_ABIMASK
) == elf_header.EF_ARM_ABI_VER5
result &= (
elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD
) == elf_header.EF_ARM_ABI_FLOAT_HARD
return result


Expand All @@ -460,9 +532,9 @@ def _is_linux_i686():
elf_header = _get_elf_header()
if elf_header is None:
return False
result = elf_header[4:5] == [1] # 32-bit ELF
result &= elf_header[5:6] == [1] # little-endian
result &= elf_header[18:20] == [0x03, 0] # x86 machine
result = elf_header.e_ident_class == elf_header.ELFCLASS32
result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB
result &= elf_header.e_machine == elf_header.EM_386
return result


Expand Down
9 changes: 8 additions & 1 deletion tests/build-hello-world.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ if [ $# -eq 0 ]; then
docker run --rm -v $(pwd):/home/hello-world arm32v7/debian /home/hello-world/build-hello-world.sh incontainer
docker run --rm -v $(pwd):/home/hello-world i386/debian /home/hello-world/build-hello-world.sh incontainer
docker run --rm -v $(pwd):/home/hello-world debian /home/hello-world/build-hello-world.sh x32
cp -f hello-world-x86_64-i386 hello-world-invalid-magic
printf "\x00" | dd of=hello-world-invalid-magic bs=1 seek=0x00 count=1 conv=notrunc
cp -f hello-world-x86_64-i386 hello-world-invalid-class
printf "\x00" | dd of=hello-world-invalid-class bs=1 seek=0x04 count=1 conv=notrunc
cp -f hello-world-x86_64-i386 hello-world-invalid-data
printf "\x00" | dd of=hello-world-invalid-data bs=1 seek=0x05 count=1 conv=notrunc
head -c 40 hello-world-x86_64-i386 > hello-world-too-short
exit 0
fi

Expand All @@ -24,5 +31,5 @@ else
fi
NAME=hello-world-$(uname -m)-${ARCH}
gcc -Os -s -o ${NAME}-full hello-world.c
head -c 40 ${NAME}-full > ${NAME}
head -c 52 ${NAME}-full > ${NAME}
rm -f ${NAME}-full
Binary file modified tests/hello-world-armv7l-armel
Binary file not shown.
Binary file modified tests/hello-world-armv7l-armhf
Binary file not shown.
Binary file added tests/hello-world-invalid-class
Binary file not shown.
Binary file added tests/hello-world-invalid-data
Binary file not shown.
Binary file added tests/hello-world-invalid-magic
Binary file not shown.
Binary file added tests/hello-world-too-short
Binary file not shown.
Binary file modified tests/hello-world-x86_64-i386
Binary file not shown.
Binary file modified tests/hello-world-x86_64-x32
Binary file not shown.
21 changes: 10 additions & 11 deletions tests/test_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,10 +709,9 @@ def test_linux_platforms_manylinux2014_armv6l(monkeypatch):
)
def test_linux_platforms_not_manylinux_abi(monkeypatch, machine, abi, alt_machine):
monkeypatch.setattr(tags, "_is_manylinux_compatible", lambda name, _: True)
if platform.system() != "Linux" or platform.machine() != machine:
monkeypatch.setattr(
distutils.util, "get_platform", lambda: "linux_{}".format(machine)
)
monkeypatch.setattr(
distutils.util, "get_platform", lambda: "linux_{}".format(machine)
)
monkeypatch.setattr(
sys,
"executable",
Expand All @@ -726,14 +725,14 @@ def test_linux_platforms_not_manylinux_abi(monkeypatch, machine, abi, alt_machin


@pytest.mark.parametrize(
"content",
[None, "", "invalid_elf", "invalid_elf_invalid_elf_invalid_elf_invalid_elf"],
"content", [None, "invalid-magic", "invalid-class", "invalid-data", "too-short"]
)
def test_get_elf_header_bad_excutable(monkeypatch, tmpdir, content):
path = tmpdir.join("executable")
if content is not None:
path.write(content)
monkeypatch.setattr(sys, "executable", str(path))
def test_get_elf_header_bad_excutable(monkeypatch, content):
if content:
path = os.path.join(os.path.dirname(__file__), "hello-world-{}".format(content))
else:
path = None
monkeypatch.setattr(sys, "executable", path)
assert tags._get_elf_header() is None


Expand Down

0 comments on commit 8f7f708

Please sign in to comment.