1#!/usr/bin/env python3

2

3# The MIT License (MIT)

4#

5# Copyright (c) 2014 Anders Høst

6#

7# Permission is hereby granted, free of charge, to any person obtaining a copy

8# of this software and associated documentation files (the "Software"), to deal

9# in the Software without restriction, including without limitation the rights

10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

11# copies of the Software, and to permit persons to whom the Software is

12# furnished to do so, subject to the following conditions:

13#

14# The above copyright notice and this permission notice shall be included in

15# all copies or substantial portions of the Software.

16#

17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

23# SOFTWARE.

24

25# Modifications: 2019 Jeffrey Lane (jeffrey.lane@canonical.com)

26

27 import ctypes

28 import platform

29 import sys

30 from ctypes import c_uint32, c_int, c_size_t, c_void_p, POINTER, CFUNCTYPE

31 from subprocess import check_output

32

33# Posix x86_64:

34# Three first call registers : RDI, RSI, RDX

35# Volatile registers : RAX, RCX, RDX, RSI, RDI, R8-11

36

37# Windows x86_64:

38# Three first call registers : RCX, RDX, R8

39# Volatile registers : RAX, RCX, RDX, R8-11

40

41# cdecl 32 bit:

42# Three first call registers : Stack (%esp)

43# Volatile registers : EAX, ECX, EDX

44

45# fmt: off

46_POSIX_64_OPC = [

47 0x53, # push %rbx

48 0x89, 0xf0, # mov %esi,%eax

49 0x89, 0xd1, # mov %edx,%ecx

50 0x0f, 0xa2, # cpuid

51 0x89, 0x07, # mov %eax,(%rdi)

52 0x89, 0x5f, 0x04, # mov %ebx,0x4(%rdi)

53 0x89, 0x4f, 0x08, # mov %ecx,0x8(%rdi)

54 0x89, 0x57, 0x0c, # mov %edx,0xc(%rdi)

55 0x5b, # pop %rbx

56 0xc3 # retq

57]

58# fmt: off

59_CDECL_32_OPC = [

60 0x53, # push %ebx

61 0x57, # push %edi

62 0x8b, 0x7c, 0x24, 0x0c, # mov 0xc(%esp),%edi

63 0x8b, 0x44, 0x24, 0x10, # mov 0x10(%esp),%eax

64 0x8b, 0x4c, 0x24, 0x14, # mov 0x14(%esp),%ecx

65 0x0f, 0xa2, # cpuid

66 0x89, 0x07, # mov %eax,(%edi)

67 0x89, 0x5f, 0x04, # mov %ebx,0x4(%edi)

68 0x89, 0x4f, 0x08, # mov %ecx,0x8(%edi)

69 0x89, 0x57, 0x0c, # mov %edx,0xc(%edi)

70 0x5f, # pop %edi

71 0x5b, # pop %ebx

72 0xc3 # ret

73]

74

75 is_64bit = ctypes.sizeof(ctypes.c_voidp) == 8

76

77# EAX bitmap explaination

78# [31:28] Reserved

79# [27:20] Extended Family

80# [19:16] Extended Model

81# [15:14] Reserved

82# [13:12] Processor Type

83# [11:8] Family

84# [7:4] Model

85# [3:0] Stepping

86

87# Intel CPUID Hex Mapping

88# 0x[1][2][3][4][5]

89# [1] Extended Model

90# [2] Extended Family

91# [3] Family

92# [4] Model

93# [5] Stepping

94

95# AMD CPUID Hex Mapping

96# 0x[1][2][3][4][5][6]

97# [1] Extended Family

98# [2] Extended Model

99# [3] Reserved

100# [4] Base Family

101# [5] Base Model

102# [6] Stepping

103

104

105 class CPUID_struct(ctypes.Structure):

106 _fields_ = [(r, c_uint32) for r in ("eax", "ebx", "ecx", "edx")]

107

108

109 class CPUID(object):

110 def __init__(self):

111 if platform.machine() not in ("AMD64", "x86_64", "x86", "i686"):

112 print("ERROR: Only available for x86")

113 sys.exit(1)

114

115 opc = _POSIX_64_OPC if is_64bit else _CDECL_32_OPC

116

117 size = len(opc)

118 code = (ctypes.c_ubyte * size)(*opc)

119

120 self.libc = ctypes.cdll.LoadLibrary(None)

121 self.libc.valloc.restype = ctypes.c_void_p

122 self.libc.valloc.argtypes = [ctypes.c_size_t]

123 self.addr = self.libc.valloc(size)

124 if not self.addr:

125 print("ERROR: Could not allocate memory")

126 sys.exit(1)

127

128 self.libc.mprotect.restype = c_int

129 self.libc.mprotect.argtypes = [c_void_p, c_size_t, c_int]

130 ret = self.libc.mprotect(self.addr, size, 1 | 2 | 4)

131 if ret != 0:

132 print("ERROR: Failed to set RWX")

133 sys.exit(1)

134

135 ctypes.memmove(self.addr, code, size)

136

137 func_type = CFUNCTYPE(None, POINTER(CPUID_struct), c_uint32, c_uint32)

138 self.func_ptr = func_type(self.addr)

139

140 def __call__(self, eax, ecx=0):

141 struct = CPUID_struct()

142 self.func_ptr(struct, eax, ecx)

143 return struct.eax, struct.ebx, struct.ecx, struct.edx

144

145 def __del__(self):

146 # Seems to throw exception when the program ends and

147 # libc is cleaned up before the object?

148 self.libc.free.restype = None

149 self.libc.free.argtypes = [c_void_p]

150 self.libc.free(self.addr)

151

152

153 def cpuid_to_human_friendly(cpuid: str) -> str:

154 """

155 Translate the cpuid into a human friendly name.

156

157 :param cpuid: cpuid in "hex" form, like "0x806c1"

158 :raises KeyError: when the cpuid is not in the database.

159

160 The "database" is a the big dict below.

161

162 The dict inefficiency in this implementation is obvious. The lookup is

163 done over the cpuid, so that should be the key. For now let's keep it

164 consistent with how it was in the past.

165 """

166 # let's disable the formatting so the structure is easier to follow

167 # fmt: off

168 CPUIDS = {

169 "Amber Lake": ['0x806e9'],

170 "AMD EPYC": ['0x800f12'],

171 "AMD Genoa": ['0xa10f11'],

172 "AMD Lisbon": ['0x100f81'],

173 "AMD Magny-Cours": ['0x100f91'],

174 "AMD Milan": ['0xa00f11'],

175 "AMD Milan-X": ['0xa00f12'],

176 "AMD ROME": ['0x830f10'],

177 "AMD Ryzen": ['0x810f81'],

178 "AMD Bergamo": ['0xaa0f01'],

179 "AMD Siena SP6": ['0xaa0f02'],

180 "AMD Raphael": ['0xa60f12'],

181 "AMD Turin": ['0xb00f21', '0xb10f10'],

182 "Broadwell": ['0x4067', '0x306d4', '0x5066', '0x406f'],

183 "Canon Lake": ['0x6066'],

184 "Cascade Lake": ['0x50655', '0x50656', '0x50657'],

185 "Coffee Lake": [

186 '0x806ea', '0x906ea', '0x906eb', '0x906ec', '0x906ed'],

187 "Comet Lake": ['0x806ec', '0xa065'],

188 "Cooper Lake": ['0x5065a', '0x5065b'],

189 "Emerald Rapids": ['0xc06f2'],

190 "Haswell": ['0x306c', '0x4065', '0x4066', '0x306f'],

191 "Hygon Dhyana Plus": ["0x900f22"],

192 "Hygon C86-4G 7490": ['0x900f41'],

193 "Ice Lake": ['0x606e6', '0x606a6', '0x706e6', '0x606c1'],

194 "Ivy Bridge": ['0x306a', '0x306e'],

195 "Kaby Lake": ['0x806e9', '0x906e9'],

196 "Knights Landing": ['0x5067'],

197 "Knights Mill": ['0x8065'],

198 "Nehalem": ['0x106a', '0x106e5', '0x206e'],

199 "Pineview": ['0x106ca'],

200 "Penryn": ['0x1067a'],

201 "Raptor Lake": ['0xb0671', '0xb06f2', '0xb06f5', '0xb06a2'],

202 "Rocket Lake": ['0xa0671'],

203 "Sandy Bridge": ['0x206a', '0x206d6', '0x206d7'],

204 "Sapphire Rapids": ['0x806f3', '0x806f6', '0x806f7', '0x806f8'],

205 "Skylake": ['0x406e3', '0x506e3', '0x50654', '0x50652'],

206 "Tiger Lake": ['0x806c1'],

207 "Alder Lake": ['0x906a4', '0x906a3', '0x90675', '0x90672'],

208 "Westmere": ['0x2065', '0x206c', '0x206f'],

209 "Whisky Lake": ['0x806eb', '0x806ec'],

210 "Sierra Forest": ['0xa06f3'],

211 "Granite Rapids": ['0xa06e0', '0xa06d0', '0xa06d1'],

212 "Meteor Lake": ['0xa06a4'],

213 "Arrow Lake": ['0xc0660'],

214 "Lunar Lake": ["0xb06d1"]

215 }

216 for key in CPUIDS.keys():

217 for value in CPUIDS[key]:

218 if value.lower() in cpuid.lower():

219 return key

220 raise ValueError("No processor found with the CPUID of {}".format(cpuid))

221

222

223 def main():

224 cpuid = CPUID()

225 cpu = cpuid(1)

226

227 # Lets play Guess The CPU!

228 # First lets get the name from /proc/cpuinfo

229 cpu_data = check_output('lscpu', universal_newlines=True).split('\n')

230 for line in cpu_data:

231 if line.startswith('Model name:'):

232 print("CPU Model: %s" % line.split(':')[1].lstrip())

233

234 my_id = (hex(cpu[0]))

235 try:

236 nice_name = cpuid_to_human_friendly(my_id)

237 print("CPUID: {} which appears to be a {} processor".format(

238 my_id, nice_name))

239 except ValueError:

240 raise SystemExit(

241 "Unable to determine CPU Family for this CPUID: {}".format(my_id))

242

243

244 if __name__ == "__main__":

245 sys.exit(main())