Set of scripts used for various projects.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

427 lines
12 KiB

  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2014, 2026 John-Mark Gurney.
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. # 1. Redistributions of source code must retain the above copyright
  10. # notice, this list of conditions and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. # notice, this list of conditions and the following disclaimer in the
  13. # documentation and/or other materials provided with the distribution.
  14. #
  15. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  16. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  18. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  19. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  21. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  22. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  23. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  24. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  25. # SUCH DAMAGE.
  26. #
  27. import enum
  28. import struct
  29. import weakref
  30. import unittest
  31. from ctypes import CDLL, Structure, POINTER, pointer, create_string_buffer, sizeof, CFUNCTYPE
  32. from ctypes import c_int, c_uint, c_char_p, c_long, c_ulong, c_size_t, c_ssize_t, c_void_p
  33. __all__ = [ 'CTF' ]
  34. _ctflib = CDLL('libctf.so.2')
  35. class CTFError(Exception):
  36. pass
  37. class HexInt(int):
  38. def __str__(self):
  39. return '%#x' % self
  40. __repr__ = __str__
  41. models = {
  42. 'CTF_MODEL_ILP32': 1,
  43. 'CTF_MODEL_LP64': 2,
  44. }
  45. globals().update(models)
  46. models.update((v, k) for k, v in tuple(models.items()))
  47. # From: sys/ctf.h
  48. kinds = {
  49. 'CTF_K_UNKNOWN': 0,
  50. 'CTF_K_INTEGER': 1,
  51. 'CTF_K_FLOAT': 2,
  52. 'CTF_K_POINTER': 3,
  53. 'CTF_K_ARRAY': 4,
  54. 'CTF_K_FUNCTION': 5,
  55. 'CTF_K_STRUCT': 6,
  56. 'CTF_K_UNION': 7,
  57. 'CTF_K_ENUM': 8,
  58. 'CTF_K_FORWARD': 9,
  59. 'CTF_K_TYPEDEF': 10,
  60. 'CTF_K_VOLATILE': 11,
  61. 'CTF_K_CONST': 12,
  62. 'CTF_K_RESTRICT': 13,
  63. }
  64. globals().update(kinds)
  65. kinds.update((v, k) for k, v in tuple(kinds.items()))
  66. intenc = {
  67. 'CTF_INT_SIGNED': 0x01,
  68. 'CTF_INT_CHAR': 0x02,
  69. 'CTF_INT_BOOL': 0x04,
  70. 'CTF_INT_VARARGS': 0x08,
  71. }
  72. globals().update(intenc)
  73. intenc.update((v, k) for k, v in tuple(intenc.items()))
  74. def lookupid(v, d):
  75. tv = d[v]
  76. try:
  77. return int(v)
  78. except ValueError:
  79. return tv
  80. def lookupstr(v, d):
  81. tv = d[v]
  82. if isinstance(tv, str):
  83. return tv
  84. else:
  85. return d[tv]
  86. class ctf_visit_f(Structure):
  87. pass
  88. class ctf_encoding_t(Structure):
  89. _fields_ = [ ('cte_format', c_uint),
  90. ('cte_offset', c_uint),
  91. ('cte_bits', c_uint),
  92. ]
  93. ctf_id_t = c_long
  94. # If c_int != 0, return immediately
  95. ctf_member_f = CFUNCTYPE(c_int, c_char_p, ctf_id_t, c_ulong, c_void_p)
  96. class ctf_file_t(Structure):
  97. pass
  98. ctf_file_t_p = POINTER(ctf_file_t)
  99. for r, f, a in (
  100. (ctf_file_t_p, 'ctf_open', (c_char_p, POINTER(c_int), )),
  101. (None, 'ctf_close', (ctf_file_t_p, )),
  102. (c_int, 'ctf_update', (ctf_file_t_p, )),
  103. (c_int, 'ctf_discard', (ctf_file_t_p, )),
  104. (c_int, 'ctf_write', (ctf_file_t_p, c_int, )),
  105. (ctf_file_t_p, 'ctf_parent_file', (ctf_file_t_p, )),
  106. (c_char_p, 'ctf_parent_name', (ctf_file_t_p, )),
  107. (c_int, 'ctf_setmodel', (ctf_file_t_p, c_int, )),
  108. (c_int, 'ctf_getmodel', (ctf_file_t_p, )),
  109. (c_int, 'ctf_errno', (ctf_file_t_p, )),
  110. (c_char_p, 'ctf_errmsg', (c_int, )),
  111. (c_int, 'ctf_version', (c_int, )),
  112. (ctf_id_t, 'ctf_lookup_by_name', (ctf_file_t_p, c_char_p, )),
  113. (ctf_id_t, 'ctf_lookup_by_symbol', (ctf_file_t_p, c_ulong, )),
  114. (ctf_id_t, 'ctf_type_resolve', (ctf_file_t_p, ctf_id_t, )),
  115. (c_ssize_t, 'ctf_type_lname', (ctf_file_t_p, ctf_id_t, c_char_p, c_size_t, )),
  116. (c_char_p, 'ctf_type_name', (ctf_file_t_p, ctf_id_t, c_char_p, c_size_t, )),
  117. (c_ssize_t, 'ctf_type_size', (ctf_file_t_p, ctf_id_t, )),
  118. (c_ssize_t, 'ctf_type_align', (ctf_file_t_p, ctf_id_t, )),
  119. (int, 'ctf_type_kind', (ctf_file_t_p, ctf_id_t, )),
  120. (ctf_id_t, 'ctf_type_reference', (ctf_file_t_p, ctf_id_t, )),
  121. (ctf_id_t, 'ctf_type_pointer', (ctf_file_t_p, ctf_id_t, )),
  122. (int, 'ctf_type_encoding', (ctf_file_t_p, ctf_id_t, POINTER(ctf_encoding_t), )),
  123. (int, 'ctf_type_visit', (ctf_file_t_p, ctf_id_t, POINTER(ctf_visit_f), c_void_p, )),
  124. (int, 'ctf_type_cmp', (ctf_file_t_p, ctf_id_t, ctf_file_t_p, ctf_id_t, )),
  125. (int, 'ctf_type_compat', (ctf_file_t_p, ctf_id_t, ctf_file_t_p, ctf_id_t, )),
  126. ):
  127. fun = getattr(_ctflib, f)
  128. setattr(fun, 'restype', r)
  129. setattr(fun, 'argtypes', a)
  130. class CTFID:
  131. def __init__(self, _ctfobj, id):
  132. self._ctfobj = _ctfobj
  133. self._ctf = _ctfobj._ctf
  134. self._id = id
  135. __int_sizes = {
  136. 1: 'B',
  137. 2: 'H',
  138. 4: 'I',
  139. 8: 'Q',
  140. }
  141. def __call__(self, s, endian='@'):
  142. size = self.tsize
  143. if len(s) < size:
  144. raise ValueError('not enough bytes, needed %d', size)
  145. base = self.resolve()
  146. kind = base.kind
  147. src, rem = s[:size], s[size:]
  148. conv = endian
  149. if kind == 'CTF_K_INTEGER':
  150. enc = base.getencoding()
  151. if enc.cte_format & intenc['CTF_INT_CHAR']:
  152. assert size == 1
  153. val = src
  154. else:
  155. convfun = lambda x: x
  156. if enc.cte_format & intenc['CTF_INT_SIGNED']:
  157. convfun = lambda x: x.lower()
  158. if enc.cte_format & intenc['CTF_INT_BOOL']:
  159. convchar = '?'
  160. else:
  161. convchar = self.__int_sizes[size]
  162. conv += convfun(convchar)
  163. val = struct.unpack(conv, src)[0]
  164. elif kind == 'CTF_K_POINTER':
  165. val = HexInt(struct.unpack(endian + self.__int_sizes[size], src)[0])
  166. elif kind == 'CTF_K_ENUM':
  167. raise NotImplementedError(kind)
  168. elif kind in ('CTF_K_STRUCT', 'CTF_K_UNION'):
  169. members = []
  170. def addmember(*args):
  171. members.append(args)
  172. return 0
  173. self.memberiter(addmember)
  174. #print repr(kind)
  175. #print repr(members)
  176. val = {}
  177. for name, _type, off in members:
  178. if off % 8 != 0:
  179. raise NotImplementedError('bytes only for now')
  180. off /= 8
  181. #print 'h:', repr(src), repr(off), repr(_type.size)
  182. val[name], trem = _type(src[off:off + _type.size], endian)
  183. assert not trem, 'trem left? %s' % repr(trem)
  184. elif kind == 'CTF_K_UNION':
  185. raise NotImplementedError(kind)
  186. else:
  187. raise NotImplementedError(kind)
  188. return val, rem
  189. def memberiter(self, fun):
  190. iterfun = self._ctfobj.makeiterfun(fun)
  191. _ctflib.ctf_member_iter(self._ctf, self._id, iterfun, None)
  192. def resolve(self):
  193. return self._ctfobj.resolve(self._id)
  194. def reference(self):
  195. return self._ctfobj.reference(self._id)
  196. def getname(self):
  197. buf = create_string_buffer(64)
  198. rbuf = _ctflib.ctf_type_name(self._ctf, self._id, buf, sizeof(buf))
  199. return rbuf
  200. @property
  201. def align(self):
  202. return _ctflib.ctf_type_align(self._ctf, self._id)
  203. @property
  204. def size(self):
  205. return _ctflib.ctf_type_size(self._ctf, self._id)
  206. @property
  207. def kind(self):
  208. return lookupstr(_ctflib.ctf_type_kind(self._ctf, self._id),
  209. kinds)
  210. def getencoding(self):
  211. enc = ctf_encoding_t()
  212. r = _ctflib.ctf_type_encoding(self._ctf, self._id, pointer(enc))
  213. if r != 0:
  214. raise self._ctfobj.geterror()
  215. return enc
  216. def __repr__(self):
  217. i = map(lambda x: ': '.join(x), [
  218. ('name', str(self.getname())),
  219. ('size', str(self.size)),
  220. ('kind', self.kind),
  221. ('align', str(self.align)),
  222. ])
  223. return '<CTFID: %s>' % ', '.join(i)
  224. class CTF:
  225. def __init__(self, f, model=None):
  226. i = c_int()
  227. f = f.encode('utf-8')
  228. self._ctf = _ctflib.ctf_open(f, pointer(i))
  229. if not bool(self._ctf):
  230. raise ValueError('%s: %s' % (repr(f),
  231. _ctflib.ctf_errmsg(i)))
  232. self._ids = weakref.WeakValueDictionary()
  233. if model is not None:
  234. _ctflib.ctf_setmodel(self._ctf, lookupid(model, models))
  235. def makeiterfun(self, fun):
  236. def lfun(name, id, off, ptr):
  237. id = self._getid(id)
  238. return fun(name, id, off)
  239. return ctf_member_f(lfun)
  240. def resolve(self, id):
  241. if isinstance(id, CTFID):
  242. id = id._id
  243. nid = _ctflib.ctf_type_resolve(self._ctf, id)
  244. return self._getid(nid)
  245. def reference(self, id):
  246. if isinstance(id, CTFID):
  247. id = id._id
  248. nid = _ctflib.ctf_type_reference(self._ctf, id)
  249. return self._getid(nid)
  250. def geterror(self):
  251. err = _ctflib.ctf_errno(self._ctf)
  252. errmsg = _ctflib.ctf_errmsg(err)
  253. return CTFError((err, errmsg))
  254. def __getitem__(self, k):
  255. if isinstance(k, str):
  256. k = k.encode('utf-8')
  257. if isinstance(k, bytes):
  258. id = _ctflib.ctf_lookup_by_name(self._ctf, k)
  259. else:
  260. id = _ctflib.ctf_lookup_by_symbol(self._ctf, k)
  261. if id == -1:
  262. raise KeyError('unknown name: %s' % repr(k))
  263. #print 'getitem:', repr(id)
  264. return self._getid(id)
  265. def _getid(self, id):
  266. try:
  267. return self._ids[id]
  268. except KeyError:
  269. obj = CTFID(self, id)
  270. self._ids[id] = obj
  271. return obj
  272. def __del__(self):
  273. try:
  274. _ctflib.ctf_close(self._ctf)
  275. except Exception:
  276. pass
  277. def dump(name, _type, off):
  278. _type = _type.resolve()
  279. print('d:', repr(name, _type, off))
  280. return 0
  281. def parseddbw(s):
  282. listofchars = [ x.split(':', 1)[1].split() for x in s.split('\n') ]
  283. words = sum(listofchars, [])
  284. return struct.pack('>%dI' % len(words), *(int(x, 16) for x in words))
  285. def parseddb(s):
  286. listofchars = [ x.split(':', 1)[1].split() for x in s.split('\n') ]
  287. chars = sum(listofchars, [])
  288. #print chars
  289. l = ''.join(chr(int(x, 16)) for x in chars)
  290. return l
  291. if __name__ == '__main__':
  292. #ctf = CTF('/boot/kernel/kernel')
  293. ctf = CTF('/tmp/avila3.ctf', model='CTF_MODEL_ILP32')
  294. #ctf = CTF('/tftpboot/kernel.avila.avila')
  295. i = ctf['int']
  296. vm = ctf['vm_page_t']
  297. vmr = vm.resolve()
  298. vmrr = vmr.reference()
  299. print('int:', repr(i))
  300. print('vm_page_t:', repr(vm))
  301. print('vm_page_t res:', repr(vmr))
  302. print('vm_page_t resolve:', repr(vmrr))
  303. #vmrr.memberiter(dump)
  304. print('sizeof(struct vm_page):', ctf['struct vm_page'].size)
  305. #print(repr(vmrr('\x11' * vmrr.size)))
  306. r = parseddb('''0xc0805db0: 0 0 0 0 c0 6d 1f 64 c0 80 5e 0 c0 6d 18 a0
  307. 0xc0805dc0: c0 6d 18 78 0 0 0 0 0 0 0 0 0 0 d f4
  308. 0xc0805dd0: 1 f 40 0 0 0 0 0 0 0 0 0 c0 df 40 0
  309. 0xc0805de0: 0 0 0 0 c0 80 5d e0 0 0 0 1 0 0 0 1
  310. 0xc0805df0: 0 0 0 0 0 4 ff 1 9 0 0 ff ff 0 0 0
  311. 0xc0805e00: 0 0 0 0 c0 6d 1f 4c c0 80 5e 50 c0 80 5d b8
  312. 0xc0805e10: c0 6d 18 78 0 0 0 0 0 0 0 0 0 0 d f5
  313. 0xc0805e20: 1 f 50 0 0 0 0 0 0 0 0 0 c0 df 50 0''')
  314. r2 = parseddbw('''3229638064: 0 c06d1f64 c0805e00 c06d18a0
  315. 3229638080: c06d1878 0 0 df4
  316. 3229638096: 10f4000 0 0 c0df4000
  317. 3229638112: 0 c0805de0 1 1
  318. 3229638128: 0 4ff01 90000ff ff000000''')
  319. r2 = parseddbw('''0xc07e73b0: 0 c06e6a4c c07e7400 c06e63a0
  320. 0xc07e73c0: c06e6378 0 0 dfd
  321. 0xc07e73d0: 8fd000 0 0 c0dfd000
  322. 0xc07e73e0: 0 c07e73e0 1 1
  323. 0xc07e73f0: 0 4ff02 90000ff ff000000
  324. 0xc07e7400: 0 c06e6a58 c07e7450 c07e73b8''')
  325. print(len(r), repr(r))
  326. import pprint
  327. s, r = vmrr(r, '>')
  328. pprint.pprint(s)
  329. s, r = vmrr(r2, '>')
  330. pprint.pprint(s)
  331. vm = ctf['vm_object_t'].resolve()
  332. print(repr(vm))
  333. print('sizeof(struct md_page):', ctf['struct md_page'].size)
  334. print('sizeof(vm_object_t):', ctf['vm_object_t'].size)
  335. print('sizeof(vm_pindex_t):', ctf['vm_pindex_t'].size)
  336. print('sizeof(vm_paddr_t):', ctf['vm_paddr_t'].size)
  337. class CTCTests(unittest.TestCase):
  338. def test_basic(self):
  339. ctf = CTF('/boot/kernel/kernel')
  340. i = ctf['int']
  341. self.assertEqual(i.size, 4)
  342. self.assertEqual(i.kind, 'CTF_K_INTEGER')
  343. vm = ctf['vm_page_t']
  344. self.assertEqual(vm.kind, 'CTF_K_TYPEDEF')
  345. vmr = vm.resolve()
  346. self.assertEqual(vmr.kind, 'CTF_K_POINTER')
  347. vmrr = vmr.reference()
  348. self.assertEqual(vmrr.kind, 'CTF_K_STRUCT')