diff --git a/ctf.py b/ctf.py new file mode 100644 index 0000000..54e4f04 --- /dev/null +++ b/ctf.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python +# +# Copyright 2014 John-Mark Gurney. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# +# $Id$ +# + +import struct +import weakref + +from ctypes import CDLL, Structure, POINTER, pointer, create_string_buffer, sizeof, CFUNCTYPE +from ctypes import c_int, c_uint, c_char_p, c_long, c_ulong, c_size_t, c_ssize_t, c_void_p + +__all__ = [ 'CTF' ] + +_ctflib = CDLL('libctf.so.2') +#_ctflib = CDLL('/usr/obj/arm.armeb/usr/src.avila/tmp/usr/src.avila/cddl/lib/libctf/libctf.so.2') + +class CTFError(Exception): + pass + +class HexInt(long): + def __str__(self): + return '%#x' % self + + __repr__ = __str__ + +models = { + 'CTF_MODEL_ILP32': 1, + 'CTF_MODEL_LP64': 2, +} +models.update((v, k) for k, v in models.items()) + +# From: sys/ctf.h +kinds = { + 'CTF_K_UNKNOWN': 0, + 'CTF_K_INTEGER': 1, + 'CTF_K_FLOAT': 2, + 'CTF_K_POINTER': 3, + 'CTF_K_ARRAY': 4, + 'CTF_K_FUNCTION': 5, + 'CTF_K_STRUCT': 6, + 'CTF_K_UNION': 7, + 'CTF_K_ENUM': 8, + 'CTF_K_FORWARD': 9, + 'CTF_K_TYPEDEF': 10, + 'CTF_K_VOLATILE': 11, + 'CTF_K_CONST': 12, + 'CTF_K_RESTRICT': 13, +} +kinds.update((v, k) for k, v in kinds.items()) + +intenc = { + 'CTF_INT_SIGNED': 0x01, + 'CTF_INT_CHAR': 0x02, + 'CTF_INT_BOOL': 0x04, + 'CTF_INT_VARARGS': 0x08, +} +intenc.update((v, k) for k, v in intenc.items()) + +def lookupid(v, d): + tv = d[v] + try: + return int(v) + except ValueError: + return tv + +def lookupstr(v, d): + tv = d[v] + if isinstance(tv, str): + return tv + else: + return d[tv] + +class ctf_visit_f(Structure): + pass + +class ctf_encoding_t(Structure): + _fields_ = [ ('cte_format', c_uint), + ('cte_offset', c_uint), + ('cte_bits', c_uint), + ] + +ctf_id_t = c_long + +# If c_int != 0, return immediately +ctf_member_f = CFUNCTYPE(c_int, c_char_p, ctf_id_t, c_ulong, c_void_p) + +class ctf_file_t(Structure): + pass + +ctf_file_t_p = POINTER(ctf_file_t) + +for r, f, a in ( + (ctf_file_t_p, 'ctf_open', (c_char_p, POINTER(c_int), )), + (None, 'ctf_close', (ctf_file_t_p, )), + (c_int, 'ctf_update', (ctf_file_t_p, )), + (c_int, 'ctf_discard', (ctf_file_t_p, )), + (c_int, 'ctf_write', (ctf_file_t_p, c_int, )), + + (ctf_file_t_p, 'ctf_parent_file', (ctf_file_t_p, )), + (c_char_p, 'ctf_parent_name', (ctf_file_t_p, )), + + (c_int, 'ctf_setmodel', (ctf_file_t_p, c_int, )), + (c_int, 'ctf_getmodel', (ctf_file_t_p, )), + + (c_int, 'ctf_errno', (ctf_file_t_p, )), + (c_char_p, 'ctf_errmsg', (c_int, )), + (c_int, 'ctf_version', (c_int, )), + + (ctf_id_t, 'ctf_lookup_by_name', (ctf_file_t_p, c_char_p, )), + (ctf_id_t, 'ctf_lookup_by_symbol', (ctf_file_t_p, c_ulong, )), + + (ctf_id_t, 'ctf_type_resolve', (ctf_file_t_p, ctf_id_t, )), + (c_ssize_t, 'ctf_type_lname', (ctf_file_t_p, ctf_id_t, c_char_p, c_size_t, )), + (c_char_p, 'ctf_type_name', (ctf_file_t_p, ctf_id_t, c_char_p, c_size_t, )), + (c_ssize_t, 'ctf_type_size', (ctf_file_t_p, ctf_id_t, )), + (c_ssize_t, 'ctf_type_align', (ctf_file_t_p, ctf_id_t, )), + (int, 'ctf_type_kind', (ctf_file_t_p, ctf_id_t, )), + (ctf_id_t, 'ctf_type_reference', (ctf_file_t_p, ctf_id_t, )), + (ctf_id_t, 'ctf_type_pointer', (ctf_file_t_p, ctf_id_t, )), + (int, 'ctf_type_encoding', (ctf_file_t_p, ctf_id_t, POINTER(ctf_encoding_t), )), + (int, 'ctf_type_visit', (ctf_file_t_p, ctf_id_t, POINTER(ctf_visit_f), c_void_p, )), + (int, 'ctf_type_cmp', (ctf_file_t_p, ctf_id_t, ctf_file_t_p, ctf_id_t, )), + (int, 'ctf_type_compat', (ctf_file_t_p, ctf_id_t, ctf_file_t_p, ctf_id_t, )), + ): + fun = getattr(_ctflib, f) + setattr(fun, 'restype', r) + setattr(fun, 'argtypes', a) + +class CTFID: + def __init__(self, _ctfobj, id): + self._ctfobj = _ctfobj + self._ctf = _ctfobj._ctf + self._id = id + + __int_sizes = { + 1: 'B', + 2: 'H', + 4: 'I', + 8: 'Q', + } + + def __call__(self, s, endian='@'): + size = self.getsize() + + if len(s) < size: + raise ValueError('not enough bytes, needed %d', size) + + base = self.resolve() + kind = base.getkind() + + src, rem = s[:size], s[size:] + + conv = endian + if kind == 'CTF_K_INTEGER': + enc = base.getencoding() + if enc.cte_format & intenc['CTF_INT_CHAR']: + assert size == 1 + val = src + else: + convfun = lambda x: x + if enc.cte_format & intenc['CTF_INT_SIGNED']: + convfun = lambda x: x.lower() + + if enc.cte_format & intenc['CTF_INT_BOOL']: + convchar = '?' + else: + convchar = self.__int_sizes[size] + + conv += convfun(convchar) + val = struct.unpack(conv, src)[0] + elif kind == 'CTF_K_POINTER': + val = HexInt(struct.unpack(endian + self.__int_sizes[size], src)[0]) + elif kind == 'CTF_K_ENUM': + raise NotImplementedError(kind) + elif kind in ('CTF_K_STRUCT', 'CTF_K_UNION'): + members = [] + def addmember(*args): + members.append(args) + return 0 + + self.memberiter(addmember) + + #print `kind` + #print `members` + val = {} + for name, _type, off in members: + if off % 8 != 0: + raise NotImplementedError('bytes only for now') + off /= 8 + #print 'h:', `src`, `off`, `_type.getsize()` + val[name], trem = _type(src[off:off + _type.getsize()], endian) + assert not trem, 'trem left? %s' % `trem` + elif kind == 'CTF_K_UNION': + raise NotImplementedError(kind) + else: + raise NotImplementedError(kind) + + return val, rem + + def memberiter(self, fun): + iterfun = self._ctfobj.makeiterfun(fun) + _ctflib.ctf_member_iter(self._ctf, self._id, iterfun, None) + + def resolve(self): + return self._ctfobj.resolve(self._id) + + def reference(self): + return self._ctfobj.reference(self._id) + + def getname(self): + buf = create_string_buffer(64) + rbuf = _ctflib.ctf_type_name(self._ctf, self._id, buf, sizeof(buf)) + return rbuf + + def getalign(self): + return _ctflib.ctf_type_align(self._ctf, self._id) + + def getsize(self): + return _ctflib.ctf_type_size(self._ctf, self._id) + + def getkind(self): + return lookupstr(_ctflib.ctf_type_kind(self._ctf, self._id), + kinds) + + def getencoding(self): + enc = ctf_encoding_t() + r = _ctflib.ctf_type_encoding(self._ctf, self._id, pointer(enc)) + if r != 0: + raise self._ctfobj.geterror() + + return enc + + def __repr__(self): + i = map(lambda x: ': '.join(x), [ + ('name', str(self.getname())), + ('size', str(self.getsize())), + ('kind', self.getkind()), + ('align', str(self.getalign())), + ]) + return '' % ', '.join(i) + +class CTF: + def __init__(self, f, model=None): + i = c_int() + self._ctf = _ctflib.ctf_open(f, pointer(i)) + if not bool(self._ctf): + raise ValueError('%s: %s' % (`f`, + _ctflib.ctf_errmsg(i))) + + self._ids = weakref.WeakValueDictionary() + + if model is not None: + _ctflib.ctf_setmodel(self._ctf, lookupid(model, models)) + + def makeiterfun(self, fun): + def lfun(name, id, off, ptr): + id = self._getid(id) + return fun(name, id, off) + + return ctf_member_f(lfun) + + def resolve(self, id): + if isinstance(id, CTFID): + id = id._id + + nid = _ctflib.ctf_type_resolve(self._ctf, id) + return self._getid(nid) + + def reference(self, id): + if isinstance(id, CTFID): + id = id._id + + nid = _ctflib.ctf_type_reference(self._ctf, id) + return self._getid(nid) + + def geterror(self): + err = _ctflib.ctf_errno(self._ctf) + errmsg = _ctflib.ctf_errmsg(err) + return CTFError((err, errmsg)) + + def __getitem__(self, k): + if isinstance(k, str): + id = _ctflib.ctf_lookup_by_name(self._ctf, k) + else: + id = _ctflib.ctf_lookup_by_symbol(self._ctf, k) + + if id == -1: + raise KeyError('unknown name: %s' % `k`) + #print 'getitem:', `id` + return self._getid(id) + + def _getid(self, id): + try: + return self._ids[id] + except KeyError: + obj = CTFID(self, id) + self._ids[id] = obj + + return obj + + def __del__(self): + try: + _ctflib.ctf_close(self._ctf) + except Exception: + pass + +def dump(name, _type, off): + _type = _type.resolve() + print 'd:', `name, _type, off` + return 0 + +def parseddbw(s): + listofchars = [ x.split(':', 1)[1].split() for x in s.split('\n') ] + + words = sum(listofchars, []) + return struct.pack('>%dI' % len(words), *(int(x, 16) for x in words)) + +def parseddb(s): + listofchars = [ x.split(':', 1)[1].split() for x in s.split('\n') ] + + chars = sum(listofchars, []) + #print chars + l = ''.join(chr(int(x, 16)) for x in chars) + + return l + +if __name__ == '__main__': + #ctf = CTF('/boot/kernel/kernel') + ctf = CTF('/tmp/avila3.ctf', model='CTF_MODEL_ILP32') + #ctf = CTF('/tftpboot/kernel.avila.avila') + + i = ctf['int'] + vm = ctf['vm_page_t'] + vmr = vm.resolve() + vmrr = vmr.reference() + print 'int:', `i` + print 'vm_page_t:', `vm` + print 'vm_page_t res:', `vmr` + print 'vm_page_t resolve:', `vmrr` + #vmrr.memberiter(dump) + + print 'sizeof(struct vm_page):', ctf['struct vm_page'].getsize() + #print `vmrr('\x11' * vmrr.getsize())` + r = parseddb('''0xc0805db0: 0 0 0 0 c0 6d 1f 64 c0 80 5e 0 c0 6d 18 a0 +0xc0805dc0: c0 6d 18 78 0 0 0 0 0 0 0 0 0 0 d f4 +0xc0805dd0: 1 f 40 0 0 0 0 0 0 0 0 0 c0 df 40 0 +0xc0805de0: 0 0 0 0 c0 80 5d e0 0 0 0 1 0 0 0 1 +0xc0805df0: 0 0 0 0 0 4 ff 1 9 0 0 ff ff 0 0 0 +0xc0805e00: 0 0 0 0 c0 6d 1f 4c c0 80 5e 50 c0 80 5d b8 +0xc0805e10: c0 6d 18 78 0 0 0 0 0 0 0 0 0 0 d f5 +0xc0805e20: 1 f 50 0 0 0 0 0 0 0 0 0 c0 df 50 0''') + r2 = parseddbw('''3229638064: 0 c06d1f64 c0805e00 c06d18a0 +3229638080: c06d1878 0 0 df4 +3229638096: 10f4000 0 0 c0df4000 +3229638112: 0 c0805de0 1 1 +3229638128: 0 4ff01 90000ff ff000000''') + r2 = parseddbw('''0xc07e73b0: 0 c06e6a4c c07e7400 c06e63a0 +0xc07e73c0: c06e6378 0 0 dfd +0xc07e73d0: 8fd000 0 0 c0dfd000 +0xc07e73e0: 0 c07e73e0 1 1 +0xc07e73f0: 0 4ff02 90000ff ff000000 +0xc07e7400: 0 c06e6a58 c07e7450 c07e73b8''') + print len(r), `r` + import pprint + s, r = vmrr(r, '>') + pprint.pprint(s) + s, r = vmrr(r2, '>') + pprint.pprint(s) + + vm = ctf['vm_object_t'].resolve() + print `vm` + print 'sizeof(struct md_page):', ctf['struct md_page'].getsize() + print 'sizeof(vm_object_t):', ctf['vm_object_t'].getsize() + print 'sizeof(vm_pindex_t):', ctf['vm_pindex_t'].getsize() + print 'sizeof(vm_paddr_t):', ctf['vm_paddr_t'].getsize()