Skip to content

Instantly share code, notes, and snippets.

@throwaway96
Created February 28, 2023 04:25
Show Gist options
  • Save throwaway96/b0d5b21da2a15834d916f462fea5b585 to your computer and use it in GitHub Desktop.
Save throwaway96/b0d5b21da2a15834d916f462fea5b585 to your computer and use it in GitHub Desktop.
Python script for exploring auxiliary vectors (auxv) on Linux
#!/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