1# Keystone Python bindings, by Nguyen Anh Quynnh <aquynh@gmail.com>
2import sys
3_python2 = sys.version_info[0] < 3
4if _python2:
5    range = xrange
6
7PY_EXTRA_VERSION = ".1"
8
9from . import arm_const, arm64_const, mips_const, sparc_const, hexagon_const, ppc_const, systemz_const, x86_const
10from .keystone_const import *
11
12from ctypes import *
13from platform import system
14from os.path import split, join, dirname, exists
15import distutils.sysconfig, sys
16
17
18import inspect
19if not hasattr(sys.modules[__name__], '__file__'):
20    __file__ = inspect.getfile(inspect.currentframe())
21
22_lib_path = split(__file__)[0]
23_all_libs = ('keystone.dll', 'libkeystone.so', 'libkeystone.dylib')
24_found = False
25
26for _lib in _all_libs:
27    try:
28        _lib_file = join(_lib_path, _lib)
29        #print(">> 2: Trying to load %s" %_lib_file);
30        _ks = cdll.LoadLibrary(_lib_file)
31        _found = True
32        break
33    except OSError:
34        pass
35
36if _found == False:
37    # try loading from default paths
38    for _lib in _all_libs:
39        try:
40            #print(">> 1: Trying to load %s" %_lib);
41            _ks = cdll.LoadLibrary(_lib)
42            _found = True
43            break
44        except OSError:
45            pass
46
47if _found == False:
48    # last try: loading from python lib directory
49    _lib_path = distutils.sysconfig.get_python_lib()
50    for _lib in _all_libs:
51        try:
52            _lib_file = join(_lib_path, 'keystone', _lib)
53            #print(">> 2: Trying to load %s" %_lib_file);
54            _ks = cdll.LoadLibrary(_lib_file)
55            _found = True
56            break
57        except OSError:
58            pass
59
60# Attempt Darwin specific load (10.11 specific),
61# since LD_LIBRARY_PATH is not guaranteed to exist
62if (_found == False) and (system() == 'Darwin'):
63    _lib_path = '/usr/local/lib/'
64    for _lib in _all_libs:
65        try:
66            _lib_file = join(_lib_path, _lib)
67            #print(">> 3: Trying to load %s" %_lib_file);
68            _ks = cdll.LoadLibrary(_lib_file)
69            _found = True
70            break
71        except OSError:
72            pass
73
74if _found == False:
75    raise ImportError("ERROR: fail to load the dynamic library.")
76
77__version__ = "%s.%s%s" %(KS_API_MAJOR, KS_API_MINOR, PY_EXTRA_VERSION)
78
79# setup all the function prototype
80def _setup_prototype(lib, fname, restype, *argtypes):
81    getattr(lib, fname).restype = restype
82    getattr(lib, fname).argtypes = argtypes
83
84kserr = c_int
85ks_engine = c_void_p
86ks_hook_h = c_size_t
87
88_setup_prototype(_ks, "ks_version", c_uint, POINTER(c_int), POINTER(c_int))
89_setup_prototype(_ks, "ks_arch_supported", c_bool, c_int)
90_setup_prototype(_ks, "ks_open", kserr, c_uint, c_uint, POINTER(ks_engine))
91_setup_prototype(_ks, "ks_close", kserr, ks_engine)
92_setup_prototype(_ks, "ks_strerror", c_char_p, kserr)
93_setup_prototype(_ks, "ks_errno", kserr, ks_engine)
94_setup_prototype(_ks, "ks_option", kserr, ks_engine, c_int, c_size_t)
95# int ks_asm(ks_engine *ks, const char *string, uint64_t address, unsigned char **encoding, size_t *encoding_size, size_t *stat_count);
96_setup_prototype(_ks, "ks_asm", c_int, ks_engine, c_char_p, c_uint64, POINTER(POINTER(c_ubyte)), POINTER(c_size_t), POINTER(c_size_t))
97_setup_prototype(_ks, "ks_free", None, POINTER(c_ubyte))
98
99
100# access to error code via @errno of KsError
101# this also includes the @stat_count returned by ks_asm
102class KsError(Exception):
103    def __init__(self, errno, count=None):
104        self.stat_count = count
105        self.errno = errno
106        self.message = _ks.ks_strerror(self.errno)
107        if not isinstance(self.message, str) and isinstance(self.message, bytes):
108            self.message = self.message.decode('utf-8')
109
110    # retrieve @stat_count value returned by ks_asm()
111    def get_asm_count(self):
112        return self.stat_count
113
114    def __str__(self):
115        return self.message
116
117
118# return the core's version
119def ks_version():
120    major = c_int()
121    minor = c_int()
122    combined = _ks.ks_version(byref(major), byref(minor))
123    return (major.value, minor.value, combined)
124
125
126# return the binding's version
127def version_bind():
128    return (KS_API_MAJOR, KS_API_MINOR, (KS_API_MAJOR << 8) + KS_API_MINOR)
129
130
131# check to see if this engine supports a particular arch
132def ks_arch_supported(query):
133    return _ks.ks_arch_supported(query)
134
135
136class Ks(object):
137    def __init__(self, arch, mode):
138        # verify version compatibility with the core before doing anything
139        (major, minor, _combined) = ks_version()
140        if major != KS_API_MAJOR or minor != KS_API_MINOR:
141            self._ksh = None
142            # our binding version is different from the core's API version
143            raise KsError(KS_ERR_VERSION)
144
145        self._arch, self._mode = arch, mode
146        self._ksh = c_void_p()
147        status = _ks.ks_open(arch, mode, byref(self._ksh))
148        if status != KS_ERR_OK:
149            self._ksh = None
150            raise KsError(status)
151
152        if arch == KS_ARCH_X86:
153            # Intel syntax is default for X86
154            self._syntax = KS_OPT_SYNTAX_INTEL
155        else:
156            self._syntax = None
157
158
159    # destructor to be called automatically when object is destroyed.
160    def __del__(self):
161        if self._ksh:
162            try:
163                status = _ks.ks_close(self._ksh)
164                self._ksh = None
165                if status != KS_ERR_OK:
166                    raise KsError(status)
167            except: # _ks might be pulled from under our feet
168                pass
169
170
171    # return assembly syntax.
172    @property
173    def syntax(self):
174        return self._syntax
175
176
177    # syntax setter: modify assembly syntax.
178    @syntax.setter
179    def syntax(self, style):
180        status = _ks.ks_option(self._ksh, KS_OPT_SYNTAX, style)
181        if status != KS_ERR_OK:
182            raise KsError(status)
183        # save syntax
184        self._syntax = style
185
186
187    # assemble a string of assembly
188    def asm(self, string, addr = 0):
189        encode = POINTER(c_ubyte)()
190        encode_size = c_size_t()
191        stat_count = c_size_t()
192        if not isinstance(string, bytes) and isinstance(string, str):
193            string = string.encode('ascii')
194
195        status = _ks.ks_asm(self._ksh, string, addr, byref(encode), byref(encode_size), byref(stat_count))
196        if (status != 0):
197            errno = _ks.ks_errno(self._ksh)
198            raise KsError(errno, stat_count.value)
199        else:
200            if stat_count.value == 0:
201                return (None, 0)
202            else:
203                encoding = []
204                for i in range(encode_size.value):
205                    encoding.append(encode[i])
206                _ks.ks_free(encode)
207                return (encoding, stat_count.value)
208
209
210# print out debugging info
211def debug():
212    archs = { "arm": KS_ARCH_ARM, "arm64": KS_ARCH_ARM64, \
213        "mips": KS_ARCH_MIPS, "sparc": KS_ARCH_SPARC, \
214        "systemz": KS_ARCH_SYSTEMZ, "ppc": KS_ARCH_PPC, \
215        "hexagon": KS_ARCH_HEXAGON, "x86": KS_ARCH_X86 }
216
217    all_archs = ""
218    keys = archs.keys()
219    for k in sorted(keys):
220        if ks_arch_supported(archs[k]):
221            all_archs += "-%s" % k
222
223    (major, minor, _combined) = ks_version()
224
225    return "python-%s-c%u.%u-b%u.%u" % (all_archs, major, minor, KS_API_MAJOR, KS_API_MINOR)
226
227