#!/usr/bin/env python # Copyright (c) 2014-2022 Matthew Brennan Jones # Py-cpuinfo gets CPU info with pure Python # It uses the MIT License # It is hosted at: https://github.com/workhorsy/py-cpuinfo # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import os, sys import glob import platform import multiprocessing import subprocess try: import _winreg as winreg except ImportError as err: try: import winreg except ImportError as err: pass is_windows = platform.system().lower() == 'windows' out_file_name = 'system_info.txt' out_file = open(out_file_name, 'w') def run_and_get_stdout(command, pipe_command=None): from subprocess import Popen, PIPE if not pipe_command: p1 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) output = p1.communicate()[0] output = output.decode(encoding='UTF-8') return p1.returncode, output else: p1 = Popen(command, stdout=PIPE, stderr=PIPE, stdin=PIPE) p2 = Popen(pipe_command, stdin=p1.stdout, stdout=PIPE, stderr=PIPE) p1.stdout.close() output = p2.communicate()[0] output = output.decode(encoding='UTF-8') return p2.returncode, output def program_paths(program_name): paths = [] exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep)) for p in os.environ['PATH'].split(os.pathsep): p = os.path.join(p, program_name) if os.access(p, os.X_OK): paths.append(p) for e in exts: pext = p + e if os.access(pext, os.X_OK): paths.append(pext) return paths def has_sestatus(): return len(program_paths('sestatus')) > 0 def sestatus_b(): return run_and_get_stdout(['sestatus', '-b']) def _is_selinux_enforcing(): # Just return if the SE Linux Status Tool is not installed if not has_sestatus(): return False # Run the sestatus, and just return if it failed to run returncode, output = sestatus_b() if returncode != 0: return False # Figure out if explicitly in enforcing mode for line in output.splitlines(): line = line.strip().lower() if line.startswith("current mode:"): if line.endswith("enforcing"): return True else: return False # Figure out if we can execute heap and execute memory can_selinux_exec_heap = False can_selinux_exec_memory = False for line in output.splitlines(): line = line.strip().lower() if line.startswith("allow_execheap") and line.endswith("on"): can_selinux_exec_heap = True elif line.startswith("allow_execmem") and line.endswith("on"): can_selinux_exec_memory = True return (not can_selinux_exec_heap or not can_selinux_exec_memory) def parse_arch(arch_string_raw): import re arch, bits = None, None arch_string_raw = arch_string_raw.lower() # X86 if re.match(r'^i\d86$|^x86$|^x86_32$|^i86pc$|^ia32$|^ia-32$|^bepc$', arch_string_raw): arch = 'X86_32' bits = 32 elif re.match(r'^x64$|^x86_64$|^x86_64t$|^i686-64$|^amd64$|^ia64$|^ia-64$', arch_string_raw): arch = 'X86_64' bits = 64 # ARM elif re.match(r'^armv8-a|aarch64$', arch_string_raw): arch = 'ARM_8' bits = 64 elif re.match(r'^armv7$|^armv7[a-z]$|^armv7-[a-z]$|^armv6[a-z]$', arch_string_raw): arch = 'ARM_7' bits = 32 elif re.match(r'^armv8$|^armv8[a-z]$|^armv8-[a-z]$', arch_string_raw): arch = 'ARM_8' bits = 32 # PPC elif re.match(r'^ppc32$|^prep$|^pmac$|^powermac$', arch_string_raw): arch = 'PPC_32' bits = 32 elif re.match(r'^powerpc$|^ppc64$|^ppc64le$', arch_string_raw): arch = 'PPC_64' bits = 64 # SPARC elif re.match(r'^sparc32$|^sparc$', arch_string_raw): arch = 'SPARC_32' bits = 32 elif re.match(r'^sparc64$|^sun4u$|^sun4v$', arch_string_raw): arch = 'SPARC_64' bits = 64 # S390X elif re.match(r'^s390x$', arch_string_raw): arch = 'S390X' bits = 64 # RISCV elif re.match(r'^riscv$|^riscv32$|^riscv32be$', arch_string_raw): arch = 'RISCV_32' bits = 32 elif re.match(r'^riscv64$|^riscv64be$', arch_string_raw): arch = 'RISCV_64' bits = 64 return (arch, bits) def print_output(name, output): line = "=" * 79 out_file.write('{0}:\n{1}\n{2}\n\n\n\n'.format(name, line, output)) print_output('sys.executable', sys.executable) print_output('sys.version_info', sys.version_info) if hasattr(sys, 'maxsize'): sizes = { 2**31-1: '32 bit', 2**63-1: '64 bit', } friendly_maxsize = sizes.get(sys.maxsize) or 'unknown' print_output('sys.maxsize', "{0} ({1})".format(sys.maxsize, friendly_maxsize)) print_output('multiprocessing.cpu_count', multiprocessing.cpu_count()) if hasattr(os, 'cpu_count'): print_output('os.cpu_count', os.cpu_count()) if 'NUMBER_OF_PROCESSORS' in os.environ: print_output("os.environ['NUMBER_OF_PROCESSORS']", os.environ['NUMBER_OF_PROCESSORS']) if hasattr(os, 'sysconf'): print_output("os.sysconf('SC_NPROCESSORS_ONLN')", os.sysconf('SC_NPROCESSORS_ONLN')) if program_paths('sysctl'): returncode, output = run_and_get_stdout(['sysctl', '-n', 'hw.ncpu']) print_output('sysctl -n hw.ncpu', output) print_output('platform.uname', platform.uname()) print_output('platform.architecture', platform.architecture()) print_output('platform.system', platform.system()) print_output('platform.machine', platform.machine()) if program_paths('cpufreq-info'): returncode, output = run_and_get_stdout(['cpufreq-info']) print_output('cpufreq-info', output) if program_paths('sestatus'): returncode, output = run_and_get_stdout(['sestatus', '-b']) print_output('sestatus -b', output) if os.path.exists('/proc/cpuinfo'): returncode, output = run_and_get_stdout(['cat', '/proc/cpuinfo']) print_output('cat /proc/cpuinfo', output) if program_paths('sysctl'): returncode, output = run_and_get_stdout(['sysctl', 'machdep.cpu', 'hw.cpufrequency']) print_output('sysctl machdep.cpu hw.cpufrequency', output) if program_paths('sysctl'): returncode, output = run_and_get_stdout(['sysctl', 'hw.model', 'hw.machine']) print_output('sysctl hw.model hw.machine', output) if program_paths('isainfo'): returncode, output = run_and_get_stdout(['isainfo', '-vb']) print_output('isainfo -vb', output) if program_paths('kstat'): returncode, output = run_and_get_stdout(['kstat', '-m', 'cpu_info']) print_output('kstat -m cpu_info', output) if program_paths('lscpu'): returncode, output = run_and_get_stdout(['lscpu']) print_output('lscpu', output) if program_paths('dmesg'): returncode, output = run_and_get_stdout(['dmesg', '-a']) if returncode != 0: returncode, output = run_and_get_stdout(['dmesg']) if len(output) > 20480: output = output[0 : 20480] print_output('dmesg -a', output) if os.path.exists('/var/run/dmesg.boot'): returncode, output = run_and_get_stdout(['cat', '/var/run/dmesg.boot']) if len(output) > 20480: output = output[0 : 20480] print_output('cat /var/run/dmesg.boot', output) if program_paths('sysinfo'): uname = platform.system().strip().strip('"').strip("'").strip().lower() is_beos = 'beos' in uname or 'haiku' in uname if is_beos: returncode, output = run_and_get_stdout(['sysinfo', '-cpu']) print_output('sysinfo -cpu', output) if program_paths('lsprop'): ibm_features = glob.glob('/proc/device-tree/cpus/*/ibm,pa-features') if ibm_features: returncode, output = run_and_get_stdout(['lsprop', ibm_features[0]]) print_output('lsprop /proc/device-tree/cpus/*/ibm,pa-features', output) if program_paths('wmic'): returncode, output = run_and_get_stdout(['wmic', 'os', 'get', 'Version']) if returncode == 0 and len(output) > 0: returncode, output = run_and_get_stdout(['wmic', 'cpu', 'get', 'Name,CurrentClockSpeed,L2CacheSize,L3CacheSize,Description,Caption,Manufacturer', '/format:list']) if returncode == 0 and len(output) > 0: print_output('wmic cpu get Name,CurrentClockSpeed,L2CacheSize,L3CacheSize,Description,Caption,Manufacturer /format:list', output) if 'winreg' in sys.modules or '_winreg' in sys.modules: key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") processor_brand = winreg.QueryValueEx(key, "ProcessorNameString")[0] winreg.CloseKey(key) print_output('winreg processor_brand', processor_brand) key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") vendor_id = winreg.QueryValueEx(key, "VendorIdentifier")[0] winreg.CloseKey(key) print_output('winreg vendor_id', vendor_id) key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment") arch_string_raw = winreg.QueryValueEx(key, "PROCESSOR_ARCHITECTURE")[0] winreg.CloseKey(key) print_output('winreg arch_string_raw', arch_string_raw) key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") hz_actual = winreg.QueryValueEx(key, "~Mhz")[0] winreg.CloseKey(key) print_output('winreg hz_actual', hz_actual) key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0") feature_bits = winreg.QueryValueEx(key, "FeatureSet")[0] winreg.CloseKey(key) print_output('winreg feature_bits', feature_bits) class ASM: def __init__(self, restype=None, argtypes=(), machine_code=[]): self.restype = restype self.argtypes = argtypes self.machine_code = machine_code self.prochandle = None self.mm = None self.func = None self.address = None self.size = 0 self.is_selinux_enforcing = _is_selinux_enforcing() def compile(self): import ctypes machine_code = bytes.join(b'', self.machine_code) self.size = ctypes.c_size_t(len(machine_code)) if is_windows: # Allocate a memory segment the size of the machine code, and make it executable size = len(machine_code) # Alloc at least 1 page to ensure we own all pages that we want to change protection on if size < 0x1000: size = 0x1000 MEM_COMMIT = ctypes.c_ulong(0x1000) PAGE_READWRITE = ctypes.c_ulong(0x4) pfnVirtualAlloc = ctypes.windll.kernel32.VirtualAlloc pfnVirtualAlloc.restype = ctypes.c_void_p self.address = pfnVirtualAlloc(None, ctypes.c_size_t(size), MEM_COMMIT, PAGE_READWRITE) if not self.address: raise Exception("Failed to VirtualAlloc") # Copy the machine code into the memory segment memmove = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t)(ctypes._memmove_addr) if memmove(self.address, machine_code, size) < 0: raise Exception("Failed to memmove") # Enable execute permissions PAGE_EXECUTE = ctypes.c_ulong(0x10) old_protect = ctypes.c_ulong(0) pfnVirtualProtect = ctypes.windll.kernel32.VirtualProtect res = pfnVirtualProtect(ctypes.c_void_p(self.address), ctypes.c_size_t(size), PAGE_EXECUTE, ctypes.byref(old_protect)) if not res: raise Exception("Failed VirtualProtect") # Flush Instruction Cache # First, get process Handle if not self.prochandle: pfnGetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess pfnGetCurrentProcess.restype = ctypes.c_void_p self.prochandle = ctypes.c_void_p(pfnGetCurrentProcess()) # Actually flush cache res = ctypes.windll.kernel32.FlushInstructionCache(self.prochandle, ctypes.c_void_p(self.address), ctypes.c_size_t(size)) if not res: raise Exception("Failed FlushInstructionCache") else: from mmap import mmap, MAP_PRIVATE, MAP_ANONYMOUS, PROT_WRITE, PROT_READ, PROT_EXEC # Allocate a private and executable memory segment the size of the machine code machine_code = bytes.join(b'', self.machine_code) self.size = len(machine_code) self.mm = mmap(-1, self.size, flags=MAP_PRIVATE | MAP_ANONYMOUS, prot=PROT_WRITE | PROT_READ | PROT_EXEC) # Copy the machine code into the memory segment self.mm.write(machine_code) self.address = ctypes.addressof(ctypes.c_int.from_buffer(self.mm)) # Cast the memory segment into a function functype = ctypes.CFUNCTYPE(self.restype, *self.argtypes) self.func = functype(self.address) def run(self): # Call the machine code like a function retval = self.func() return retval def free(self): import ctypes # Free the function memory segment if is_windows: MEM_RELEASE = ctypes.c_ulong(0x8000) ctypes.windll.kernel32.VirtualFree(ctypes.c_void_p(self.address), ctypes.c_size_t(0), MEM_RELEASE) else: self.mm.close() self.prochandle = None self.mm = None self.func = None self.address = None self.size = 0 class CPUID: def __init__(self): # Figure out if SE Linux is on and in enforcing mode self.is_selinux_enforcing = _is_selinux_enforcing() def _asm_func(self, restype=None, argtypes=(), machine_code=[]): asm = ASM(restype, argtypes, machine_code) asm.compile() return asm def _run_asm(self, *machine_code): import ctypes asm = ASM(ctypes.c_uint32, (), machine_code) asm.compile() retval = asm.run() asm.free() return retval # http://en.wikipedia.org/wiki/CPUID#EAX.3D0:_Get_vendor_ID def get_vendor_id(self): rets = [] # EBX ebx = self._run_asm( b"\x31\xC0", # xor eax,eax b"\x0F\xA2" # cpuid b"\x89\xD8" # mov ax,bx b"\xC3" # ret ) #print('!!! ebx: ', hex(ebx)) rets.append(ebx) # ECX ecx = self._run_asm( b"\x31\xC0", # xor eax,eax b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) #print('!!! ecx: ', hex(ecx)) rets.append(ecx) # EDX edx = self._run_asm( b"\x31\xC0", # xor eax,eax b"\x0f\xa2" # cpuid b"\x89\xD0" # mov ax,dx b"\xC3" # ret ) #print('!!! edx: ', hex(edx)) rets.append(edx) return rets # http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits def get_info(self): # EAX eax = self._run_asm( b"\xB8\x01\x00\x00\x00", # mov eax,0x1" b"\x0f\xa2" # cpuid b"\xC3" # ret ) #print('!!! eax: ', hex(eax)) return eax # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000000h:_Get_Highest_Extended_Function_Supported def get_max_extension_support(self): # Check for extension support max_extension_support = self._run_asm( b"\xB8\x00\x00\x00\x80" # mov ax,0x80000000 b"\x0f\xa2" # cpuid b"\xC3" # ret ) return max_extension_support # http://en.wikipedia.org/wiki/CPUID#EAX.3D1:_Processor_Info_and_Feature_Bits def get_flags(self, max_extension_support): rets = [] # EDX edx = self._run_asm( b"\xB8\x01\x00\x00\x00", # mov eax,0x1" b"\x0f\xa2" # cpuid b"\x89\xD0" # mov ax,dx b"\xC3" # ret ) #print('!!! edx: ', hex(edx)) rets.append(edx) # ECX ecx = self._run_asm( b"\xB8\x01\x00\x00\x00", # mov eax,0x1" b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) #print('!!! ecx: ', hex(ecx)) rets.append(ecx) # http://en.wikipedia.org/wiki/CPUID#EAX.3D7.2C_ECX.3D0:_Extended_Features if max_extension_support >= 7: # EBX ebx = self._run_asm( b"\x31\xC9", # xor ecx,ecx b"\xB8\x07\x00\x00\x00" # mov eax,7 b"\x0f\xa2" # cpuid b"\x89\xD8" # mov ax,bx b"\xC3" # ret ) #print('!!! ebx: ', hex(ebx)) rets.append(ebx) # ECX ecx = self._run_asm( b"\x31\xC9", # xor ecx,ecx b"\xB8\x07\x00\x00\x00" # mov eax,7 b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) #print('!!! ecx: ', hex(ecx)) rets.append(ecx) # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000001h:_Extended_Processor_Info_and_Feature_Bits if max_extension_support >= 0x80000001: # EBX ebx = self._run_asm( b"\xB8\x01\x00\x00\x80" # mov ax,0x80000001 b"\x0f\xa2" # cpuid b"\x89\xD8" # mov ax,bx b"\xC3" # ret ) #print('!!! ebx: ', hex(ebx)) rets.append(ebx) # ECX ecx = self._run_asm( b"\xB8\x01\x00\x00\x80" # mov ax,0x80000001 b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) #print('!!! ecx: ', hex(ecx)) rets.append(ecx) return rets # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000002h.2C80000003h.2C80000004h:_Processor_Brand_String def get_processor_brand(self, max_extension_support): rets = [] # Processor brand string if max_extension_support >= 0x80000004: instructions = [ b"\xB8\x02\x00\x00\x80", # mov ax,0x80000002 b"\xB8\x03\x00\x00\x80", # mov ax,0x80000003 b"\xB8\x04\x00\x00\x80" # mov ax,0x80000004 ] for instruction in instructions: # EAX eax = self._run_asm( instruction, # mov ax,0x8000000? b"\x0f\xa2" # cpuid b"\x89\xC0" # mov ax,ax b"\xC3" # ret ) #print('!!! eax: ', hex(eax)) rets.append(eax) # EBX ebx = self._run_asm( instruction, # mov ax,0x8000000? b"\x0f\xa2" # cpuid b"\x89\xD8" # mov ax,bx b"\xC3" # ret ) #print('!!! ebx: ', hex(ebx)) rets.append(ebx) # ECX ecx = self._run_asm( instruction, # mov ax,0x8000000? b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) #print('!!! ecx: ', hex(ecx)) rets.append(ecx) # EDX edx = self._run_asm( instruction, # mov ax,0x8000000? b"\x0f\xa2" # cpuid b"\x89\xD0" # mov ax,dx b"\xC3" # ret ) #print('!!! edx: ', hex(edx)) rets.append(edx) return rets # http://en.wikipedia.org/wiki/CPUID#EAX.3D80000006h:_Extended_L2_Cache_Features def get_cache(self, max_extension_support): # Just return if the cache feature is not supported if max_extension_support < 0x80000006: return # ECX ecx = self._run_asm( b"\xB8\x06\x00\x00\x80" # mov ax,0x80000006 b"\x0f\xa2" # cpuid b"\x89\xC8" # mov ax,cx b"\xC3" # ret ) return ecx def get_cpu_info_from_cpuid(): # Just return if not X86 arch_string_raw = platform.machine() arch, bits = parse_arch(arch_string_raw) if not arch in ['X86_32', 'X86_64']: return {} # FIXME: Return none if SE Linux is in enforcing mode cpuid = CPUID() if cpuid.is_selinux_enforcing: return {} output = '' max_extension_support = cpuid.get_max_extension_support() output += '# max_extension_support\n' output += hex(max_extension_support) + ',\n' rets = cpuid.get_cache(max_extension_support) output += '# get_cache\n' output += hex(rets) + ',\n' rets = cpuid.get_info() output += '# get_info\n' output += hex(rets) + ',\n' rets = cpuid.get_processor_brand(max_extension_support) output += '# get_processor_brand\n' output += ', '.join([hex(n) for n in rets]) + ',\n' rets = cpuid.get_vendor_id() output += '# get_vendor_id\n' output += ', '.join([hex(n) for n in rets]) + ',\n' rets = cpuid.get_flags(max_extension_support) output += '# get_flags\n' output += ', '.join([hex(n) for n in rets]) + ',\n' return output try: output = get_cpu_info_from_cpuid() print_output('CPUID', output) except Exception: pass out_file.close() print('System info written to "{0}"'.format(out_file_name))