ghidra/Ghidra/Extensions/SleighDevTools/pcodetest/pcodetest.py

444 lines
18 KiB
Python

import os
import glob
import re
import fnmatch
from build import Config, BuildUtil
class PCodeTest(BuildUtil):
defaults = Config()
list = { }
def __init__(self, conf):
super(PCodeTest, self).__init__()
self.config = Config(PCodeTest.defaults, conf)
# calculate the toolchain_dir
self.config.toolchain_dir = self.config.format('%(toolchain_root)s/%(toolchain)s-gcc-%(gcc_version)s')
if not self.isdir(self.config.toolchain_dir):
self.config.toolchain_dir = self.config.format('%(toolchain_root)s/%(toolchain)s')
(self.config.toolchain_family, self.config.install_target) = self.config.toolchain.split('/')
if not self.config.target: self.config.target = self.config.install_target
# can default the Processor directory name, usually the
# initial string of 'language_id' (otherwise unused).
if self.config.language_id:
self.config.processor = self.config.language_id.split(':')[0]
# expand all of the variables with printf escapes
self.config.expand()
# save the new PCodeTest in a dictionary for auto-enumeration
PCodeTest.list[self.config.name] = self
@classmethod
def print_all(cls):
pct = sorted(cls.list.iteritems(), key=lambda x: x[0].lower())
for t,pcodetest in sorted(cls.list.iteritems(), key=lambda x: x[0].lower()):
print str(pcodetest)
if pcodetest.config.verbose: print pcodetest.config.dump()
def __str__(self):
cb = 'build-all:%-5s' % ('yes' if self.config.build_all else 'no')
ce = 'can-export:%-5s' % ('yes' if self.config.can_export else 'no')
ct = 'compiler-type:%-5s' % self.config.toolchain_type
tc = 'Toolchain:%s' % self.config.toolchain
return self.config.name.ljust(20) + cb + ce + ct + tc
class PCodeTestBuild(BuildUtil):
def __init__(self, pcode_test):
super(PCodeTestBuild, self).__init__()
self.config = Config(pcode_test.config)
self.config.cwd = self.getcwd()
@classmethod
def factory(cls, pcode_test):
if pcode_test.config.toolchain_type == 'gcc':
return PCodeBuildGCC(pcode_test)
elif pcode_test.config.toolchain_type == 'ccs':
return PCodeBuildCCS(pcode_test)
elif pcode_test.config.toolchain_type == 'sdcc':
return PCodeBuildSDCC(pcode_test)
else:
raise Exception(self.config.format('Toolchain type %(toolchain_type)s not known'))
def which(self, what):
return self.config.format('%(toolchain_dir)s/%(' + what + ')s')
def compile(self, input_files, opt_cflag, output_base):
self.log_err(self.config.format('compile not implemented for %(toolchain_type)s'))
# generic build a single PCodeTest for all variants
def main(self):
# make sure compiler exists and runnable
if not self.is_executable_file(self.which('compile_exe')):
self.log_err(self.config.format('build the Toolchain before compilation'))
return
# save path to tpp
tpp_py = os.getcwd() + '/tpp.py'
# Get a list of strings to filter input files
available_files = sorted(glob.glob(self.config.format('%(pcodetest_src)s/*')))
# skip any?
skip_files = self.config.skip_files
if len(skip_files) > 0:
toskip = [x for x in available_files if self.basename(x) in skip_files]
if len(toskip) != len(skip_files):
self.log_warn('These files will not be skipped because they are not in the build: %s'
% ' '.join([x for x in skip_files if not x in toskip]))
available_files = [x for x in available_files if not x in toskip]
# remove float/double/longlong files if not supported
if not self.config.has_float: available_files = [x for x in available_files if not fnmatch.fnmatch(x, '*FLOAT*')]
if not self.config.has_double: available_files = [x for x in available_files if not fnmatch.fnmatch(x, '*DOUBLE*')]
if not self.config.has_longlong: available_files = [x for x in available_files if not fnmatch.fnmatch(x, '*LONGLONG*')]
# compile for each optimization
for opt_name,opt_cflag in sorted(self.config.variants.iteritems()):
kind = 'PCodeTest'
# This is the base name of the binary file, or for small
# build, the directory name that will hold the small
# binaries
out_name = '%s_%s_%s_pcodetest' % (self.config.name, self.config.toolchain_type.upper(), opt_name)
if self.config.architecture_test: pcodetest_base_name = self.config.architecture_test
else: pcodetest_base_name = self.config.architecture
pcodetest_test = '%s_%s_EmulatorTest' % (pcodetest_base_name, opt_name)
# GNUMake like rule to prevent un-required builds of pcodetests files
# This does not rebuild if the output directory is newer than the
# input files. So it needs to know where the build
# directory would be, before it is recreated.
build_dir = self.build_dir(self.config.build_root, kind, out_name)
need_to_build = self.config.force or not self.isdir(build_dir)
if not need_to_build:
mtime = self.getmtime(build_dir)
for f in available_files:
if mtime < self.getmtime(f):
need_to_build = True
break
if not need_to_build:
self.log_info('%s up to date (call with --force to force build)' % self.log_prefix(kind, out_name))
continue
self.open_log(self.config.build_root, kind, out_name, chdir=True)
# copy source files to build directory, and go there
for f in available_files: self.copy(f, '.', verbose=False)
# if requested, add an info file
if self.config.add_info: self.mkinfo('INFO.c')
# make tests, if needed
for f_test in glob.glob('*.test'):
f_h = re.sub(r'[.]test', '.h', f_test)
if self.isfile(f_h) and self.getmtime(f_test) <= self.getmtime(f_h): continue
out, err = self.run(['python', tpp_py, f_test])
if err:
self.log_err(err)
out, err = self.run(['python', tpp_py, '--entry', 'pcode_main.c'])
if err:
self.log_err(err)
if self.num_errors > 0:
self.chdir(self.config.cwd)
self.log_close()
continue
if self.config.small_build:
# For a small build, build a binary for every
# _BODY.c file in the smallFiles list.
smallFiles = sorted(glob.glob('*_BODY.c'))
self.log_info('**** SMALL BUILD ****')
# Remove the previous directory, if it was there
build_dir = '%s/build-PCodeTest-%s/%s' % (self.config.build_root, out_name, out_name)
try: self.rmtree(build_dir)
except: pass
# Each small file ends with _BODY.c and it has a
# companion without _BODY.
for body_file in smallFiles:
small_name = body_file.replace('_BODY.c', '')
companion_file = small_name + '.c'
if not self.isfile(companion_file) or not self.isfile(body_file):
self.log_info('Skipping %s %s build' % (companion_file, body_file))
continue
input_files = ['pcode_test.c', 'pcode_main.c', 'builtin.c', companion_file, body_file]
self.compile(input_files, opt_cflag, small_name)
self.export_file(small_name+'.out', build_dir)
# export the directory
target_dir = self.config.export_root+'%s'%out_name
self.log_info("Exporting %s directory to %s" % (build_dir, target_dir) )
self.export_file(build_dir, target_dir)
else:
# compile all the c and h files here
input_files = sorted(glob.glob('*.[c]'))
self.compile(input_files, opt_cflag, out_name)
# export the file
target_dir = self.config.export_root
self.log_info("Exporting file to %s" % target_dir)
output_file = '%s.out' % (out_name)
self.export_file(output_file, target_dir)
self.chdir(self.config.cwd)
self.log_close()
class PCodeBuildSDCC(PCodeTestBuild):
def __init__(self, PCodeTest):
super(PCodeBuildSDCC, self).__init__(PCodeTest)
# Set options for compiler depending on needs.
def cflags(self, output_file):
f = []
f += ['-DHAS_FLOAT=1' if self.config.has_float else '-DHAS_FLOAT_OVERRIDE=1']
f += ['-DHAS_DOUBLE=1' if self.config.has_double else '-DHAS_DOUBLE_OVERRIDE=1']
f += ['-DHAS_LONGLONG=1' if self.config.has_longlong else '-DHAS_LONGLONG_OVERRIDE=1']
if self.config.has_shortfloat: f += ['-DHAS_SHORTFLOAT=1']
if self.config.has_vector: f += ['-DHAS_VECTOR=1']
if self.config.has_decimal128: f += ['-DHAS_DECIMAL128=1']
if self.config.has_decimal32: f += ['-DHAS_DECIMAL32=1']
if self.config.has_decimal64: f += ['-DHAS_DECIMAL64=1']
f += ['-DNAME=NAME:%s' % output_file]
f += self.config.ccflags.split()
f += self.config.add_ccflags.split()
return f
def compile(self, input_files, opt_cflag, output_base):
# Name the output file, and delete it if it exists
output_file = '%s.out' % (output_base)
self.remove(output_file)
# Construct the compile command line and execute it
cmp = self.which('compile_exe')
cmd = [cmp] + input_files + self.cflags(output_file)
if opt_cflag: cmd += [opt_cflag]
cmd += ['-o', output_file]
out, err = self.run(cmd)
if out: self.log_info(out)
# print error messages, which may just be warnings
if err: self.log_warn(err)
# return now if the error preempted the binary
if not self.is_readable_file(output_file):
self.log_err('output not created %s' % output_file)
return
class PCodeBuildCCS(PCodeTestBuild):
def __init__(self, PCodeTest):
super(PCodeBuildCCS, self).__init__(PCodeTest)
# Set options for compiler depending on needs.
def cflags(self, output_file):
f = []
f += ['-DHAS_FLOAT=1' if self.config.has_float else '-DHAS_FLOAT_OVERRIDE=1']
f += ['-DHAS_DOUBLE=1' if self.config.has_double else '-DHAS_DOUBLE_OVERRIDE=1']
f += ['-DHAS_LONGLONG=1' if self.config.has_longlong else '-DHAS_LONGLONG_OVERRIDE=1']
if self.config.has_shortfloat: f += ['-DHAS_SHORTFLOAT=1']
if self.config.has_vector: f += ['-DHAS_VECTOR=1']
if self.config.has_decimal128: f += ['-DHAS_DECIMAL128=1']
if self.config.has_decimal32: f += ['-DHAS_DECIMAL32=1']
if self.config.has_decimal64: f += ['-DHAS_DECIMAL64=1']
f += ['-DNAME=NAME:%s' % output_file]
f += self.config.ccflags.split()
f += self.config.add_ccflags.split()
return f
def compile(self, input_files, opt_cflag, output_base):
# Name the output file, and delete it if it exists
output_file = '%s.out' % (output_base)
self.remove(output_file)
# Construct the compile command line and execute it
cmp = self.which('compile_exe')
cmd = [cmp] + input_files + self.cflags(output_file) + [opt_cflag]
cmd += ['-z', '-h', '-e', 'printf5']
cmd += [self.config.format('%(toolchain_dir)s/tools/compiler/ti-cgt-msp430_16.9.0.LTS/lib/libc.a')]
cmd += ['-o', output_file]
out, err = self.run(cmd)
if out: self.log_info(out)
# print error messages, which may just be warnings
if err: self.log_warn(err)
# return now if the error preempted the binary
if not self.is_readable_file(output_file):
self.log_err('output not created %s' % output_file)
return
class PCodeBuildGCC(PCodeTestBuild):
def __init__(self, PCodeTest):
super(PCodeBuildGCC, self).__init__(PCodeTest)
self.saved_ld_library_path = self.getenv('LD_LIBRARY_PATH', '')
# add a new option to library path, or reset to saved value
def set_library_path(self, add):
if add and self.saved_ld_library_path:
self.environment('LD_LIBRARY_PATH', '%s:%s' % (self.config.ld_library_path, add))
elif add:
self.environment('LD_LIBRARY_PATH', add)
elif self.saved_ld_library_path:
self.environment('LD_LIBRARY_PATH', self.saved_ld_library_path)
# Create all the associated files for a output.
def associated_info(self, bin, base):
out, err = self.run(['file', bin])
if out: self.log_info(out)
if err:
self.log_err(err)
out, err = self.run([self.which('objdump_exe')]
+ self.config.objdump_option.split()
+ ['-d', bin], stdout=('%s.d' % base))
if err: self.log_warn(err)
out, err = self.run([self.which('objdump_exe')]
+ self.config.objdump_option.split()
+ ['-s', '--section', '.comment', bin],
stdout=('%s.comment' % base))
if err: self.log_warn(err)
out, err = self.run([self.which('objdump_exe')]
+ self.config.objdump_option.split()
+ ['-x', '-s', '-j', '.data', '-j', '.rodata', '-t' , bin],
stdout=('%s.mem' % base))
if err: self.log_warn(err)
out, err = self.run([self.which('readelf_exe'),
'--debug-dump=decodedline', bin],
stdout=('%s.li' % base))
if err: self.log_warn(err)
out, err = self.run([self.which('nm_exe'), '-a', bin],
stdout=('%s.nm' % base))
if err: self.log_warn(err)
out, err = self.run([self.which('readelf_exe'), '-a', bin],
stdout=('%s.readelf' % base))
if err: self.log_warn(err)
out, err = self.run(['grep', ' U ', '%s.nm' % base])
if out: self.log_warn('** UNRESOLVED:\n' + out + '**END')
if err: self.log_warn(err)
# Set options for compiler depending on needs.
def cflags(self, output_file):
f = []
f += ['-DHAS_FLOAT=1' if self.config.has_float else '-DHAS_FLOAT_OVERRIDE=1']
f += ['-DHAS_DOUBLE=1' if self.config.has_double else '-DHAS_DOUBLE_OVERRIDE=1']
f += ['-DHAS_LONGLONG=1' if self.config.has_longlong else '-DHAS_LONGLONG_OVERRIDE=1']
if self.config.has_shortfloat: f += ['-DHAS_SHORTFLOAT=1']
if self.config.has_vector: f += ['-DHAS_VECTOR=1']
if self.config.has_decimal128: f += ['-DHAS_DECIMAL128=1']
if self.config.has_decimal32: f += ['-DHAS_DECIMAL32=1']
if self.config.has_decimal64: f += ['-DHAS_DECIMAL64=1']
f += ['-DNAME=NAME:%s' % output_file]
# turn off -g because dwarf, not needed
f += ['-dA', '-w']
# for xc26: f += ['--no-data-init']
# or maybe f += ['-Xlinker', '--no-data-init']
# This helps to alleviate undefined main, etc
f += ['--entry', 'main']
f += ['-static', '-Wno-unused-macros', '-nodefaultlibs', '-nostartfiles', '-fno-builtin']
# can pass this if weak symbols aren't defined
# f += ['-Xlinker', '--unresolved-symbols=ignore-all']
f += self.config.ccflags.split()
f += self.config.add_ccflags.split()
return f
def compile(self, input_files, opt_cflag, output_base):
# Name the output file, and delete it if it exists
output_file = '%s.out' % (output_base)
self.remove(output_file)
# set the library path
self.set_library_path(self.config.ld_library_path)
# Construct the compile/link command line and execute it
cmp = self.which('compile_exe')
cmd = [cmp] + input_files + self.cflags(output_file) + [opt_cflag, '-B', self.dirname(cmp), '-o', output_file]
out, err = self.run(cmd)
if out: self.log_info(out)
# print error messages, which may just be warnings
if err: self.log_warn(err)
# but return now if the error preempted the binary
if not self.is_readable_file(output_file):
self.log_err('output not created %s' % output_file)
return
# strip
if self.config.strip_symbols:
str = self.which('strip_exe')
cmd = [str, '-s', output_file]
out, err = self.run(cmd)
if out: self.log_info(out)
# Get associated information (identify file, output-file.d,
# .li, .nm, and .readelf, identify file, unresolves symbols)
self.associated_info(output_file, output_base)
# build a BUILD_EXE version
if self.config.build_exe:
cmp = self.which('compile_exe')
cmd = [cmp] + input_files + self.cflags(output_file)\
+ ['-DBUILD_EXE', opt_cflag, '-B', self.dirname(cmp), '-o', '%s.exe' % output_base]
out, err = self.run(cmd)
if err: self.log_warn(err)
if out: self.log_info(out)
if self.config.qemu_command:
build_dir = self.build_dir(self.config.build_root, "pcodetest", output_base)
self.log_info(self.config.format('%s %s/%s.exe' %(self.config.qemu_command, build_dir, output_base)))