Created
February 28, 2023 04:25
-
-
Save throwaway96/b0d5b21da2a15834d916f462fea5b585 to your computer and use it in GitHub Desktop.
Python script for exploring auxiliary vectors (auxv) on Linux
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
# Combined into a single file for the sake of portability. | |
# Only tested with Python 3.10 and 3.11; may or may not also work with 3.9. | |
# Tested on x86-64, AArch32, and AArch64. | |
from __future__ import annotations | |
from typing import Final, NamedTuple, Type | |
import sys | |
import struct | |
import ctypes | |
from enum import Enum, Flag | |
class Hwcap(Flag): | |
pass | |
# from linux/arch/x86/include/uapi/asm/hwcap2.h | |
class X86Hwcap2(Hwcap, Flag): | |
# MONITOR/MWAIT enabled in Ring 3 | |
HWCAP2_RING3MWAIT = 1 << 0 | |
# Kernel allows FSGSBASE instructions available in Ring 3 | |
HWCAP2_FSGSBASE = 1 << 1 | |
# from linux/arch/arm/include/uapi/asm/hwcap.h | |
class ArmHwcap(Hwcap, Flag): | |
# HWCAP flags - for elf_hwcap (in kernel) and AT_HWCAP | |
HWCAP_SWP = 1 << 0 | |
HWCAP_HALF = 1 << 1 | |
HWCAP_THUMB = 1 << 2 | |
HWCAP_26BIT = 1 << 3 # Play it safe | |
HWCAP_FAST_MULT = 1 << 4 | |
HWCAP_FPA = 1 << 5 | |
HWCAP_VFP = 1 << 6 | |
HWCAP_EDSP = 1 << 7 | |
HWCAP_JAVA = 1 << 8 | |
HWCAP_IWMMXT = 1 << 9 | |
HWCAP_CRUNCH = 1 << 10 # Obsolete | |
HWCAP_THUMBEE = 1 << 11 | |
HWCAP_NEON = 1 << 12 | |
HWCAP_VFPv3 = 1 << 13 | |
HWCAP_VFPv3D16 = 1 << 14 # also set for VFPv4-D16 | |
HWCAP_TLS = 1 << 15 | |
HWCAP_VFPv4 = 1 << 16 | |
HWCAP_IDIVA = 1 << 17 | |
HWCAP_IDIVT = 1 << 18 | |
HWCAP_VFPD32 = 1 << 19 # set if VFP has 32 regs (not 16) | |
HWCAP_IDIV = HWCAP_IDIVA | HWCAP_IDIVT | |
HWCAP_LPAE = 1 << 20 | |
HWCAP_EVTSTRM = 1 << 21 | |
HWCAP_FPHP = 1 << 22 | |
HWCAP_ASIMDHP = 1 << 23 | |
HWCAP_ASIMDDP = 1 << 24 | |
HWCAP_ASIMDFHM = 1 << 25 | |
HWCAP_ASIMDBF16 = 1 << 26 | |
HWCAP_I8MM = 1 << 27 | |
# from linux/arch/arm/include/uapi/asm/hwcap.h | |
class ArmHwcap2(Hwcap, Flag): | |
# HWCAP2 flags - for elf_hwcap2 (in kernel) and AT_HWCAP2 | |
HWCAP2_AES = 1 << 0 | |
HWCAP2_PMULL = 1 << 1 | |
HWCAP2_SHA1 = 1 << 2 | |
HWCAP2_SHA2 = 1 << 3 | |
HWCAP2_CRC32 = 1 << 4 | |
HWCAP2_SB = 1 << 5 | |
HWCAP2_SSBS = 1 << 6 | |
# from linux/arch/arm64/include/uapi/asm/hwcap.h | |
class Arm64Hwcap(Hwcap, Flag): | |
# HWCAP flags - for AT_HWCAP | |
# Bits 62 and 63 are reserved for use by libc. | |
# Bits 32-61 are unallocated for potential use by libc. | |
HWCAP_FP = 1 << 0 | |
HWCAP_ASIMD = 1 << 1 | |
HWCAP_EVTSTRM = 1 << 2 | |
HWCAP_AES = 1 << 3 | |
HWCAP_PMULL = 1 << 4 | |
HWCAP_SHA1 = 1 << 5 | |
HWCAP_SHA2 = 1 << 6 | |
HWCAP_CRC32 = 1 << 7 | |
HWCAP_ATOMICS = 1 << 8 | |
HWCAP_FPHP = 1 << 9 | |
HWCAP_ASIMDHP = 1 << 10 | |
HWCAP_CPUID = 1 << 11 | |
HWCAP_ASIMDRDM = 1 << 12 | |
HWCAP_JSCVT = 1 << 13 | |
HWCAP_FCMA = 1 << 14 | |
HWCAP_LRCPC = 1 << 15 | |
HWCAP_DCPOP = 1 << 16 | |
HWCAP_SHA3 = 1 << 17 | |
HWCAP_SM3 = 1 << 18 | |
HWCAP_SM4 = 1 << 19 | |
HWCAP_ASIMDDP = 1 << 20 | |
HWCAP_SHA512 = 1 << 21 | |
HWCAP_SVE = 1 << 22 | |
HWCAP_ASIMDFHM = 1 << 23 | |
HWCAP_DIT = 1 << 24 | |
HWCAP_USCAT = 1 << 25 | |
HWCAP_ILRCPC = 1 << 26 | |
HWCAP_FLAGM = 1 << 27 | |
HWCAP_SSBS = 1 << 28 | |
HWCAP_SB = 1 << 29 | |
HWCAP_PACA = 1 << 30 | |
HWCAP_PACG = 1 << 31 | |
# from linux/arch/arm64/include/uapi/asm/hwcap.h | |
class Arm64Hwcap2(Hwcap, Flag): | |
# HWCAP2 flags - for AT_HWCAP2 | |
HWCAP2_DCPODP = 1 << 0 | |
HWCAP2_SVE2 = 1 << 1 | |
HWCAP2_SVEAES = 1 << 2 | |
HWCAP2_SVEPMULL = 1 << 3 | |
HWCAP2_SVEBITPERM = 1 << 4 | |
HWCAP2_SVESHA3 = 1 << 5 | |
HWCAP2_SVESM4 = 1 << 6 | |
HWCAP2_FLAGM2 = 1 << 7 | |
HWCAP2_FRINT = 1 << 8 | |
HWCAP2_SVEI8MM = 1 << 9 | |
HWCAP2_SVEF32MM = 1 << 10 | |
HWCAP2_SVEF64MM = 1 << 11 | |
HWCAP2_SVEBF16 = 1 << 12 | |
HWCAP2_I8MM = 1 << 13 | |
HWCAP2_BF16 = 1 << 14 | |
HWCAP2_DGH = 1 << 15 | |
HWCAP2_RNG = 1 << 16 | |
HWCAP2_BTI = 1 << 17 | |
HWCAP2_MTE = 1 << 18 | |
HWCAP2_ECV = 1 << 19 | |
HWCAP2_AFP = 1 << 20 | |
HWCAP2_RPRES = 1 << 21 | |
HWCAP2_MTE3 = 1 << 22 | |
HWCAP2_SME = 1 << 23 | |
HWCAP2_SME_I16I64 = 1 << 24 | |
HWCAP2_SME_F64F64 = 1 << 25 | |
HWCAP2_SME_I8I32 = 1 << 26 | |
HWCAP2_SME_F16F32 = 1 << 27 | |
HWCAP2_SME_B16F32 = 1 << 28 | |
HWCAP2_SME_F32F32 = 1 << 29 | |
HWCAP2_SME_FA64 = 1 << 30 | |
HWCAP2_WFXT = 1 << 31 | |
HWCAP2_EBF16 = 1 << 32 | |
HWCAP2_SVE_EBF16 = 1 << 33 | |
HWCAP2_CSSC = 1 << 34 | |
HWCAP2_RPRFM = 1 << 35 | |
HWCAP2_SVE2P1 = 1 << 36 | |
HWCAP2_SME2 = 1 << 37 | |
HWCAP2_SME2P1 = 1 << 38 | |
HWCAP2_SME_I16I32 = 1 << 39 | |
HWCAP2_SME_BI32I32 = 1 << 40 | |
HWCAP2_SME_B16B16 = 1 << 41 | |
HWCAP2_SME_F16F16 = 1 << 42 | |
class PlatformHwcap(NamedTuple): | |
hwcap_type: Type[Hwcap] | None | |
hwcap2_type: Type[Hwcap] | None | |
HWCAP_MAP: Final[dict[str, PlatformHwcap]] = { | |
"arm": PlatformHwcap(ArmHwcap, ArmHwcap2), | |
"v7l": PlatformHwcap(ArmHwcap, ArmHwcap2), | |
"arm64": PlatformHwcap(Arm64Hwcap, Arm64Hwcap2), | |
"v8l": PlatformHwcap(Arm64Hwcap, Arm64Hwcap2), | |
"x86": PlatformHwcap(None, X86Hwcap2), | |
"x86_64": PlatformHwcap(None, X86Hwcap2), | |
} | |
class ElfClass(Enum): | |
ElfClassNone = 0, # Invalid class | |
ElfClass32 = 1, # 32-bit objects | |
ElfClass64 = 2, # 64-bit objects | |
class AuxvEntry: | |
_type: AuxvType | |
_value: int | |
# XXX: Only works on little endian | |
_FORMAT_32: Final[str] = "<LL" | |
_FORMAT_64: Final[str] = "<QQ" | |
def __init__(self, type: AuxvType, value: int) -> None: | |
"""Initialize with provided type and value.""" | |
self._type = type | |
self._value = value | |
def get_type(self) -> AuxvType: | |
return self._type | |
def get_value(self) -> int: | |
return self._value | |
@classmethod | |
def parse(cls, bits: ElfClass, data: bytes) -> AuxvEntry: | |
"""Create AuxvEntry from raw bytes.""" | |
raw_type: int | |
value: int | |
if bits == ElfClass.ElfClass32: | |
(raw_type, value) = struct.unpack(cls._FORMAT_32, data) | |
elif bits == ElfClass.ElfClass64: | |
(raw_type, value) = struct.unpack(cls._FORMAT_64, data) | |
else: | |
raise ValueError("Invalid ELF class") | |
return AuxvEntry(AuxvType(raw_type), value) | |
@classmethod | |
def get_size(cls, bits: ElfClass) -> int: | |
"""Get size in bytes of auxv entry.""" | |
if bits == ElfClass.ElfClass32: | |
return struct.calcsize(cls._FORMAT_32) | |
elif bits == ElfClass.ElfClass64: | |
return struct.calcsize(cls._FORMAT_64) | |
else: | |
raise ValueError("Invalid ELF class") | |
# from glibc | |
class AuxvType(Enum): | |
"""Legal values for a_type (entry type).""" | |
AT_NULL = 0 # End of vector | |
AT_IGNORE = 1 # Entry should be ignored | |
AT_EXECFD = 2 # File descriptor of program | |
AT_PHDR = 3 # Program headers for program | |
AT_PHENT = 4 # Size of program header entry | |
AT_PHNUM = 5 # Number of program headers | |
AT_PAGESZ = 6 # System page size | |
AT_BASE = 7 # Base address of interpreter | |
AT_FLAGS = 8 # Flags | |
AT_ENTRY = 9 # Entry point of program | |
AT_NOTELF = 10 # Program is not ELF | |
AT_UID = 11 # Real uid | |
AT_EUID = 12 # Effective uid | |
AT_GID = 13 # Real gid | |
AT_EGID = 14 # Effective gid | |
AT_CLKTCK = 17 # Frequency of times() | |
# Some more special a_type values describing the hardware. | |
AT_PLATFORM = 15 # String identifying platform. | |
AT_HWCAP = 16 # Machine-dependent hints about | |
# processor capabilities. | |
# This entry gives some information about the FPU initialization | |
# performed by the kernel. | |
AT_FPUCW = 18 # Used FPU control word. | |
# Cache block sizes. | |
AT_DCACHEBSIZE = 19 # Data cache block size. | |
AT_ICACHEBSIZE = 20 # Instruction cache block size. | |
AT_UCACHEBSIZE = 21 # Unified cache block size. | |
# A special ignored value for PPC used by the kernel to control the | |
# interpretation of the AUXV. Must be > 16. | |
AT_IGNOREPPC = 22 # Entry should be ignored. | |
AT_SECURE = 23 # Boolean was exec setuid-like? | |
AT_BASE_PLATFORM = 24 # String identifying real platforms. | |
AT_RANDOM = 25 # Address of 16 random bytes. | |
AT_HWCAP2 = 26 # More machine-dependent hints about | |
# processor capabilities. | |
AT_EXECFN = 31 # Filename of executable. | |
# Pointer to the global system page used for system calls and other nice things. | |
AT_SYSINFO = 32 | |
AT_SYSINFO_EHDR = 33 # VDSO location (linux/arch/arm/include/uapi/asm/auxvec.h) | |
# Shapes of the caches. Bits 0-3 contains associativity; bits 4-7 contains | |
# log2 of line size; mask those to get cache size. | |
AT_L1I_CACHESHAPE = 34 | |
AT_L1D_CACHESHAPE = 35 | |
AT_L2_CACHESHAPE = 36 | |
AT_L3_CACHESHAPE = 37 | |
# Shapes of the caches with more room to describe them. | |
# *GEOMETRY are comprised of cache line size in bytes in the bottom 16 bits | |
# and the cache associativity in the next 16 bits. | |
AT_L1I_CACHESIZE = 40 | |
AT_L1I_CACHEGEOMETRY = 41 | |
AT_L1D_CACHESIZE = 42 | |
AT_L1D_CACHEGEOMETRY = 43 | |
AT_L2_CACHESIZE = 44 | |
AT_L2_CACHEGEOMETRY = 45 | |
AT_L3_CACHESIZE = 46 | |
AT_L3_CACHEGEOMETRY = 47 | |
AT_MINSIGSTKSZ = 51 # Stack needed for signal delivery | |
def main() -> None: | |
is_64bits: bool = sys.maxsize > 2**32 | |
bits: ElfClass = ElfClass.ElfClass64 if is_64bits else ElfClass.ElfClass32 | |
entry_size: int = AuxvEntry.get_size(bits) | |
platform: str | None = None | |
hwcap_raw: int | None = None | |
hwcap2_raw: int | None = None | |
with open("/proc/self/auxv", "rb") as f: | |
while True: | |
data: bytes = f.read(entry_size) | |
entry: AuxvEntry = AuxvEntry.parse(bits, data) | |
type: AuxvType = entry.get_type() | |
type_name: str = type.name | |
value: int = entry.get_value() | |
print(f"{type_name}: {value:#x}") | |
if type == AuxvType.AT_NULL: | |
print("got AT_NULL, exiting") | |
break | |
elif type == AuxvType.AT_PLATFORM: | |
platform = ctypes.string_at(value).decode("ascii") | |
elif type == AuxvType.AT_HWCAP: | |
hwcap_raw = value | |
elif type == AuxvType.AT_HWCAP2: | |
hwcap2_raw = value | |
# blank line | |
print() | |
if platform is None: | |
print("did not find AT_PLATFORM entry") | |
return | |
print(f"platform: '{platform}'") | |
if platform not in HWCAP_MAP: | |
print("platform not found in hwcap map") | |
return | |
if hwcap_raw is None: | |
print("did not find AT_HWCAP entry") | |
else: | |
hwcap_type: Type[Hwcap] | None = HWCAP_MAP[platform].hwcap_type | |
if hwcap_type is None: | |
print("no hwcap info for platform") | |
else: | |
hwcap: Hwcap = hwcap_type(hwcap_raw) | |
print(f"hwcap: {str(hwcap)}") | |
if hwcap2_raw is None: | |
print("did not find AT_HWCAP2 entry") | |
else: | |
hwcap2_type: Type[Hwcap] | None = HWCAP_MAP[platform].hwcap2_type | |
if hwcap2_type is None: | |
print("no hwcap2 info for platform") | |
else: | |
hwcap2: Hwcap = hwcap2_type(hwcap2_raw) | |
print(f"hwcap2: {str(hwcap2)}") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment