# Keystone Engine # Adapted from the code of Dang Hoang Vu for Capstone Engine, 2013 from __future__ import print_function import sys, re, os INCL_DIR = os.path.join('..', 'include', 'keystone') # NOTE: this reflects the value of KS_ERR_ASM_xxx in keystone.h ks_err_val = { 'KS_ERR_ASM': '128', 'KS_ERR_ASM_ARCH': '512' } include = [ 'arm.h', 'arm64.h', 'mips.h', 'x86.h', 'sparc.h', 'ppc.h', 'systemz.h', 'hexagon.h', 'evm.h', 'keystone.h' ] def CamelCase(s): # return re.sub(r'(\w)+\_?', lambda m:m.group(0).capitalize(), s) return ''.join(''.join([w[0].upper(), w[1:].lower()]) for w in s.split('_')) template = { 'powershell': { 'header': "/// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [%s_h.cs]\n", 'footer': "", 'out_file': './powershell/Keystone/Const/%s_h.cs', # prefixes for constant filenames of all archs - case sensitive 'keystone.h': 'keystone', 'comment_open': '///', 'comment_close': '', 'rules': [ { 'regex': r'.*', 'line_format': 'KS_{0} = {1},\n', 'fn': (lambda x: x), }, ] }, 'rust': { 'header': "#![allow(non_camel_case_types)]\n// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [%s_const.rs]\nuse ::libc::*;\n", 'footer': "", # prefixes for constant filenames of all archs - case sensitive 'arm.h': 'keystone', 'arm64.h': 'keystone', 'mips.h': 'keystone', 'x86.h': 'keystone', 'sparc.h': 'keystone', 'systemz.h': 'keystone', 'ppc.h': 'keystone', 'hexagon.h': 'keystone', 'evm.h': 'keystone', 'keystone.h': 'keystone', 'comment_open': '/*', 'comment_close': '*/', 'out_file': './rust/keystone-sys/src/%s_const.rs', 'rules': [ { 'regex': r'(API)_.*', 'pre': '\n', 'line_format': 'pub const {0}: c_uint = {1};\n', 'fn': (lambda x: x), }, { 'regex': r'MODE_.*', 'pre': '\n' + 'bitflags! {{\n' + '#[repr(C)]\n' + ' pub struct Mode: c_int {{\n', 'line_format': ' const {0} = {1};\n', 'fn': (lambda x: '_'.join(x.split('_')[1:]) if not re.match(r'MODE_\d+', x) else x), 'post': ' }\n}', }, { 'regex': r'ARCH_.*', 'pre': '\n' + '#[repr(C)]\n' + '#[derive(Debug, PartialEq, Clone, Copy)]\n' + 'pub enum Arch {{\n', 'line_format': ' {0} = {1},\n', 'fn': (lambda x: '_'.join(x.split('_')[1:])), 'post': '}\n', }, { 'regex': r'(OPT_([A-Z]+)|OPT_SYM_RESOLVER)$', 'pre': '#[repr(C)]\n' + '#[derive(Debug, PartialEq, Clone, Copy)]\n' + 'pub enum OptionType {{\n', 'line_format': ' {0} = {1},\n', 'fn': (lambda x: '_'.join(x.split('_')[1:])), 'post': '}\n', }, { 'regex': r'OPT_(?!SYM)([A-Z]+\_)+[A-Z]+', 'pre': 'bitflags! {{\n' '#[repr(C)]\n' + ' pub struct OptionValue: size_t {{\n', 'line_format': ' const {0} = {1};\n', 'fn': (lambda x: '_'.join(x.split('_')[1:])), 'post': ' }\n}\n', }, { 'regex': r'ERR_(.*)', 'pre': 'bitflags! {{\n' + '#[repr(C)]\n' + ' pub struct Error: c_int {{\n', 'line_format': ' const {0} = {1};\n', 'fn': (lambda x: '_'.join(x.split('_')[1:])), 'post': ' }\n}', }, ], }, 'go': { 'header': "package keystone\n// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [%s_const.go]\n\n", 'footer': "", # prefixes for constant filenames of all archs - case sensitive 'arm.h': 'arm', 'arm64.h': 'arm64', 'mips.h': 'mips', 'x86.h': 'x86', 'sparc.h': 'sparc', 'systemz.h': 'systemz', 'ppc.h': 'ppc', 'hexagon.h': 'hexagon', 'evm.h': 'evm', 'keystone.h': 'keystone', 'comment_open': '/*', 'comment_close': '*/', 'out_file': './go/keystone/%s_const.go', 'rules': [ { 'regex': r'API_.*', 'pre': 'const (\n', 'line_format': '\t{0} = {1}\n', 'fn': (lambda x: x), 'post': ')\n', }, { 'regex': r'MODE_.*', 'pre': 'const (\n', 'line_format': '\t{0} Mode = {1}\n', 'fn': (lambda x: x), 'post': ')\n', }, { 'regex': r'ARCH_.*', 'pre': 'const (\n', 'line_format': '\t{0} Architecture = {1}\n', 'fn': (lambda x: x), 'post': ')\n', }, { 'regex': r'OPT_([A-Z]+)$', 'pre': 'const (\n', 'line_format': '\t{0} OptionType = {1}\n', 'fn': (lambda x: x), 'post': ')\n', }, { 'regex': r'OPT_([A-Z]+\_)+[A-Z]+', 'pre': 'const (\n', 'line_format': '\t{0} OptionValue = {1}\n', 'fn': (lambda x: x), 'post': ')\n', }, { 'regex': r'ERR_.*', 'pre': 'const (\n', 'line_format': '\t{0} Error = {1}\n', 'fn': (lambda x: x), 'post': ')\n', }, ] }, 'python': { 'header': "# For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [%s_const.py]\n", 'footer': "", 'out_file': './python/keystone/%s_const.py', # prefixes for constant filenames of all archs - case sensitive 'arm.h': 'arm', 'arm64.h': 'arm64', 'mips.h': 'mips', 'x86.h': 'x86', 'sparc.h': 'sparc', 'systemz.h': 'systemz', 'ppc.h': 'ppc', 'hexagon.h': 'hexagon', 'evm.h': 'evm', 'keystone.h': 'keystone', 'comment_open': '#', 'comment_close': '', 'rules': [ { 'regex': r'.*', 'line_format': 'KS_{0} = {1}\n', 'fn': (lambda x: x), }, ] }, 'nodejs': { 'header': "// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [%s_const.js]\n", 'footer': "", 'out_file': './nodejs/consts/%s.js', # prefixes for constant filenames of all archs - case sensitive 'arm.h': 'arm', 'arm64.h': 'arm64', 'mips.h': 'mips', 'x86.h': 'x86', 'sparc.h': 'sparc', 'systemz.h': 'systemz', 'ppc.h': 'ppc', 'hexagon.h': 'hexagon', 'evm.h': 'evm', 'keystone.h': 'keystone', 'comment_open': '//', 'comment_close': '', 'rules': [ { 'regex': r'.*', 'line_format': 'module.exports.{0} = {1}\n', 'fn': (lambda x: x), }, ] }, 'ruby': { 'header': "# For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [%s_const.rb]\n\nmodule Keystone\n", 'footer': "end", 'out_file': './ruby/keystone_gem/lib/keystone/%s_const.rb', # prefixes for constant filenames of all archs - case sensitive 'arm.h': 'arm', 'arm64.h': 'arm64', 'mips.h': 'mips', 'x86.h': 'x86', 'sparc.h': 'sparc', 'systemz.h': 'systemz', 'ppc.h': 'ppc', 'hexagon.h': 'hexagon', 'evm.h': 'evm', 'keystone.h': 'keystone', 'comment_open': '#', 'comment_close': '', 'rules': [ { 'regex': r'.*', 'line_format': '\tKS_{0} = {1}\n', 'fn': (lambda x: x), }, ] }, 'csharp': { 'header': "// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [%sConstants.cs]\nnamespace KeystoneNET\n{", 'footer': "}", 'out_file': './csharp/KeystoneNET/KeystoneNET/Constants/%sConstants.cs', # prefixes for constant filenames of all archs - case sensitive 'keystone.h': 'keystone', 'arm.h': 'arm', 'arm64.h': 'arm64', 'mips.h': 'mips', 'x86.h': 'x86', 'sparc.h': 'sparc', 'systemz.h': 'systemz', 'ppc.h': 'ppc', 'hexagon.h': 'hexagon', 'evm.h': 'evm', 'keystone.h': 'keystone', 'comment_open': '//', 'comment_close': '', 'rules': [ { 'regex': r'(ARCH)_.*', 'pre': '\n\tpublic enum KeystoneArchitecture : int\n\t{{\n', 'post': '\t}', 'line_format': '\t\tKS_{0} = {1},\n', 'fn': (lambda x: x), }, { 'regex': r'(MODE)_.*', 'pre': '\n\tpublic enum KeystoneMode : uint\n\t{{\n', 'post': '\t}', 'line_format': '\t\tKS_{0} = {1},\n', 'fn': (lambda x: x), }, { 'regex': r'(ERR)_.*', 'pre': '\n\tpublic enum {0}Error : short\n\t{{\n', 'post': '\t}', 'line_format': '\t\tKS_{0} = {1},\n', 'fn': (lambda x: x), }, { 'regex': r'((OPT_([A-Z]+))|(OPT_SYM_RESOLVER))$', 'pre': '\n\tpublic enum KeystoneOptionType : short\n\t{{\n', 'post': '\t}', 'line_format': '\t\tKS_{0} = {1},\n', 'fn': (lambda x: x), }, { 'regex': r'OPT_(?!SYM)([A-Z]+\_)+[A-Z]+', 'pre': '\n\tpublic enum KeystoneOptionValue : short\n\t{{\n', 'post': '\t}', 'line_format': '\t\tKS_{0} = {1},\n', 'fn': (lambda x: x), }, ] }, } # markup for comments to be added to autogen files MARKUP = '//>' def gen(lang): global include, INCL_DIR consts = {} templ = template[lang] for target in include: if target not in templ: continue prefix = templ[target] if target == 'keystone.h': prefix = 'keystone' lines = open(os.path.join(INCL_DIR, target)).readlines() consts[prefix] = [] previous = {} count = 0 for line in lines: line = line.strip() if line.startswith(MARKUP): # markup for comments outfile.write(("\n%s%s%s\n" %(templ['comment_open'], \ line.replace(MARKUP, ''), templ['comment_close'])).encode("utf-8")) continue if line == '' or line.startswith('//'): continue tmp = line.strip().split(',') for t in tmp: t = t.strip() if not t or t.startswith('//'): continue f = re.split('\s+', t) # parse #define KS_TARGET (num) define = False if f[0] == '#define' and len(f) >= 3: define = True f.pop(0) f.insert(1, '=') # if f[0].startswith("KS_" + prefix.upper()): if f[0].startswith("KS_"): if len(f) > 1 and f[1] not in ('//', '='): print("WARNING: Unable to convert %s" % f) print(" Line =", line) continue elif len(f) > 1 and f[1] == '=': rhs = ''.join(f[2:]) else: rhs = str(count) lhs = f[0].strip() # evaluate bitshifts in constants e.g. "KS_X86 = 1 << 1" match = re.match(r'(?P\s*\d+\s*<<\s*\d+\s*)', rhs) if match: rhs = str(eval(match.group(1))) else: # evaluate references to other constants e.g. "KS_ARM_REG_X = KS_ARM_REG_SP" match = re.match(r'^([^\d]\w+)$', rhs) if match: try: rhs = previous[match.group(1)] except: rhs = match.group(1) if not rhs.isdigit(): for k, v in previous.items(): rhs = re.sub(r'\b%s\b' % k, v, rhs) try: rhs = str(eval(rhs)) except: rhs = ks_err_val[rhs] lhs_strip = re.sub(r'^KS_', '', lhs) consts[prefix].append((lhs_strip, rhs)) count = int(rhs) + 1 previous[lhs] = str(rhs) rules = templ['rules'] for prefix in consts.keys(): outfile = open(templ['out_file'] % prefix, 'wb') # open as binary prevents windows newlines outfile.write (templ['header'] % prefix) for rule in rules: regex = rule['regex'] consts2 = [] for const in consts.get(prefix): if not (re.match(regex, const[0])): continue consts2.append(const) if len(consts2) == 0: continue if rule.get('pre'): outfile.write(rule.get('pre').format(CamelCase(prefix))) for const in consts2: lhs_strip = const[0] rhs = const[1] outfile.write(rule['line_format'].format(rule['fn'](lhs_strip), rhs, lhs_strip).encode("utf-8")) if rule.get('post'): outfile.write (rule.get('post')) outfile.write ('\n') outfile.write (templ['footer']) outfile.close() def main(): lang = sys.argv[1] if not lang in template: raise RuntimeError("Unsupported binding %s" % lang) gen(sys.argv[1]) if __name__ == "__main__": if len(sys.argv) < 2: print("Usage:", sys.argv[0], " ") sys.exit(1) main()